vendor/noxlogic/ratelimit-bundle/Noxlogic/RateLimitBundle/EventListener/RateLimitAnnotationListener.php line 53

Open in your IDE?
  1. <?php
  2. namespace Noxlogic\RateLimitBundle\EventListener;
  3. use Noxlogic\RateLimitBundle\Annotation\RateLimit;
  4. use Noxlogic\RateLimitBundle\Events\CheckedRateLimitEvent;
  5. use Noxlogic\RateLimitBundle\Events\GenerateKeyEvent;
  6. use Noxlogic\RateLimitBundle\Events\RateLimitEvents;
  7. use Noxlogic\RateLimitBundle\Exception\RateLimitExceptionInterface;
  8. use Noxlogic\RateLimitBundle\Service\RateLimitService;
  9. use Noxlogic\RateLimitBundle\Util\PathLimitProcessor;
  10. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
  14. use Symfony\Component\HttpKernel\HttpKernelInterface;
  15. use Symfony\Component\Routing\Route;
  16. class RateLimitAnnotationListener extends BaseListener
  17. {
  18.     /**
  19.      * @var eventDispatcherInterface
  20.      */
  21.     protected $eventDispatcher;
  22.     /**
  23.      * @var \Noxlogic\RateLimitBundle\Service\RateLimitService
  24.      */
  25.     protected $rateLimitService;
  26.     /**
  27.      * @var \Noxlogic\RateLimitBundle\Util\PathLimitProcessor
  28.      */
  29.     protected $pathLimitProcessor;
  30.     /**
  31.      * @param RateLimitService                    $rateLimitService
  32.      */
  33.     public function __construct(
  34.         EventDispatcherInterface $eventDispatcher,
  35.         RateLimitService $rateLimitService,
  36.         PathLimitProcessor $pathLimitProcessor
  37.     ) {
  38.         $this->eventDispatcher $eventDispatcher;
  39.         $this->rateLimitService $rateLimitService;
  40.         $this->pathLimitProcessor $pathLimitProcessor;
  41.     }
  42.     /**
  43.      * @param FilterControllerEvent $event
  44.      */
  45.     public function onKernelController(FilterControllerEvent $event)
  46.     {
  47.         // Skip if the bundle isn't enabled (for instance in test environment)
  48.         if( ! $this->getParameter('enabled'true)) {
  49.             return;
  50.         }
  51.         // Skip if we aren't the main request
  52.         if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) {
  53.             return;
  54.         }
  55.         // Find the best match
  56.         $annotations $event->getRequest()->attributes->get('_x-rate-limit', array());
  57.         $rateLimit $this->findBestMethodMatch($event->getRequest(), $annotations);
  58.         // Another treatment before applying RateLimit ?
  59.         $checkedRateLimitEvent = new CheckedRateLimitEvent($event->getRequest(), $rateLimit);
  60.         $this->eventDispatcher->dispatch(RateLimitEvents::CHECKED_RATE_LIMIT$checkedRateLimitEvent);
  61.         $rateLimit $checkedRateLimitEvent->getRateLimit();
  62.         // No matching annotation found
  63.         if (! $rateLimit) {
  64.             return;
  65.         }
  66.         $key $this->getKey($event$rateLimit$annotations);
  67.         // Ratelimit the call
  68.         $rateLimitInfo $this->rateLimitService->limitRate($key);
  69.         if (! $rateLimitInfo) {
  70.             // Create new rate limit entry for this call
  71.             $rateLimitInfo $this->rateLimitService->createRate($key$rateLimit->getLimit(), $rateLimit->getPeriod());
  72.             if (! $rateLimitInfo) {
  73.                 // @codeCoverageIgnoreStart
  74.                 return;
  75.                 // @codeCoverageIgnoreEnd
  76.             }
  77.         }
  78.         // Store the current rating info in the request attributes
  79.         $request $event->getRequest();
  80.         $request->attributes->set('rate_limit_info'$rateLimitInfo);
  81.         // Reset the rate limits
  82.         if(time() >= $rateLimitInfo->getResetTimestamp()) {
  83.             $this->rateLimitService->resetRate($key);
  84.             $rateLimitInfo $this->rateLimitService->createRate($key$rateLimit->getLimit(), $rateLimit->getPeriod());
  85.             if (! $rateLimitInfo) {
  86.                 // @codeCoverageIgnoreStart
  87.                 return;
  88.                 // @codeCoverageIgnoreEnd
  89.             }
  90.         }
  91.         // When we exceeded our limit, return a custom error response
  92.         if ($rateLimitInfo->getCalls() > $rateLimitInfo->getLimit()) {
  93.             // Throw an exception if configured.
  94.             if ($this->getParameter('rate_response_exception')) {
  95.                 $class $this->getParameter('rate_response_exception');
  96.                 $e = new $class($this->getParameter('rate_response_message'), $this->getParameter('rate_response_code'));
  97.                 if ($e instanceof RateLimitExceptionInterface) {
  98.                     $e->setPayload($rateLimit->getPayload());
  99.                 }
  100.                 throw $e;
  101.             }
  102.             $message $this->getParameter('rate_response_message');
  103.             $code $this->getParameter('rate_response_code');
  104.             $event->setController(function () use ($message$code) {
  105.                 // @codeCoverageIgnoreStart
  106.                 return new Response($message$code);
  107.                 // @codeCoverageIgnoreEnd
  108.             });
  109.             $event->stopPropagation();
  110.         }
  111.     }
  112.     /**
  113.      * @param RateLimit[] $annotations
  114.      */
  115.     protected function findBestMethodMatch(Request $request, array $annotations)
  116.     {
  117.         // Empty array, check the path limits
  118.         if (count($annotations) == 0) {
  119.             return $this->pathLimitProcessor->getRateLimit($request);
  120.         }
  121.         $best_match null;
  122.         foreach ($annotations as $annotation) {
  123.             // cast methods to array, even method holds a string
  124.             $methods is_array($annotation->getMethods()) ? $annotation->getMethods() : array($annotation->getMethods());
  125.             if (in_array($request->getMethod(), $methods)) {
  126.                 $best_match $annotation;
  127.             }
  128.             // Only match "default" annotation when we don't have a best match
  129.             if (count($annotation->getMethods()) == && $best_match == null) {
  130.                 $best_match $annotation;
  131.             }
  132.         }
  133.         return $best_match;
  134.     }
  135.     private function getKey(FilterControllerEvent $eventRateLimit $rateLimit, array $annotations)
  136.     {
  137.         // Let listeners manipulate the key
  138.         $keyEvent = new GenerateKeyEvent($event->getRequest(), ''$rateLimit->getPayload());
  139.         $rateLimitMethods join('.'$rateLimit->getMethods());
  140.         $keyEvent->addToKey($rateLimitMethods);
  141.         $rateLimitAlias count($annotations) === 0
  142.             str_replace('/''.'$this->pathLimitProcessor->getMatchedPath($event->getRequest()))
  143.             : $this->getAliasForRequest($event);
  144.         $keyEvent->addToKey($rateLimitAlias);
  145.         $this->eventDispatcher->dispatch(RateLimitEvents::GENERATE_KEY$keyEvent);
  146.         return $keyEvent->getKey();
  147.     }
  148.     private function getAliasForRequest(FilterControllerEvent $event)
  149.     {
  150.         if (($route $event->getRequest()->attributes->get('_route'))) {
  151.             return $route;
  152.         }
  153.         $controller $event->getController();
  154.         if (is_string($controller) && false !== strpos($controller'::')) {
  155.             $controller explode('::'$controller);
  156.         }
  157.         if (is_array($controller)) {
  158.             return str_replace('\\''.'is_string($controller[0]) ? $controller[0] : get_class($controller[0])) . '.' $controller[1];
  159.         }
  160.         if ($controller instanceof \Closure) {
  161.             return 'closure';
  162.         }
  163.         if (is_object($controller)) {
  164.             return str_replace('\\''.'get_class($controller[0]));
  165.         }
  166.         return 'other';
  167.     }
  168. }