src/Security/SpamGuard.php line 30

Open in your IDE?
  1. <?php namespace App\Security;
  2. use Symfony\Component\Form\FormInterface;
  3. use Symfony\Component\HttpFoundation\Request;
  4. use Symfony\Component\RateLimiter\RateLimiterFactory;
  5. /**
  6.  * Anti-spam checks shared by the public contact/order forms
  7.  * (DefaultController::contactAction/orderAction), both built on ContactType.
  8.  */
  9. class SpamGuard
  10. {
  11.     // A real visitor needs at least this long to read the form and type a
  12.     // message; bots that submit the instant the page loads don't.
  13.     private const MIN_FILL_SECONDS 3;
  14.     private RateLimiterFactory $limiter;
  15.     public function __construct(RateLimiterFactory $contactFormLimiter)
  16.     {
  17.         $this->limiter $contactFormLimiter;
  18.     }
  19.     /**
  20.      * Call when the form is rendered (not submitted), so the next
  21.      * submission's fill time can be measured against it.
  22.      */
  23.     public function markRendered(Request $requeststring $sessionKey): void
  24.     {
  25.         $request->getSession()->set($sessionKeytime());
  26.     }
  27.     /**
  28.      * True if this submission looks automated: either the honeypot field
  29.      * (invisible to real visitors, see ContactType) got filled in, or it
  30.      * arrived faster than a human could plausibly have typed a message —
  31.      * including never having loaded the form at all, which is how a bot
  32.      * that POSTs directly without a prior GET shows up here.
  33.      */
  34.     public function isSpam(Request $requestFormInterface $formstring $sessionKey): bool
  35.     {
  36.         if ($form->has('website') && $form->get('website')->getData()) {
  37.             return true;
  38.         }
  39.         $renderedAt $request->getSession()->get($sessionKey);
  40.         return $renderedAt === null || (time() - $renderedAt) < self::MIN_FILL_SECONDS;
  41.     }
  42.     /**
  43.      * Caps how many times one IP can submit within the configured window
  44.      * (see framework.yaml's rate_limiter.contact_form), independently of
  45.      * isSpam() above — catches a slower or JS-capable bot (or a determined
  46.      * human) that passes both of those checks.
  47.      */
  48.     public function isRateLimited(Request $request): bool
  49.     {
  50.         return !$this->limiter->create($request->getClientIp())->consume(1)->isAccepted();
  51.     }
  52. }