ervin11/honeypot-bundle

HoneypotBundle is a very simple way to implement a honeypot system for your symfony contact forms to block spam coming from spambots.

Installs: 9

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Forks: 0

Type:symfony-bundle

1.1 2022-06-03 15:03 UTC

This package is auto-updated.

Last update: 2025-06-29 02:00:13 UTC


README

HoneypotBundle is a very simple way to implement a honeypot system for your symfony contact forms to block spam coming from spambots.

Install the package with :

composer require ervin11/honeypot-bundle

And... that's it! If you're not using Symfony Flex, you'll also need to enable the Ervin11\HoneypotBundle\HoneypotBundle in your AppKernel.php file.

Files

This bundle provides a :

  • HoneypotTrait that provides an email field stored in database and a fakeEmail field only used for spam verification.
# Ervin11\HoneypotBundle\Traits;

trait HoneypotableTrait {

    /**
     * @ORM\Column(type="string", length=255)
     */
    protected ?string $email;

    protected string $fakeEmail;
    
    // ...
}
  • HoneypotType that you can use to add an `email and a hidden fakeEmail form field. The fakeEmail field has a callback validation that calls manageSpam` function in which spam verification is performed.
# Ervin11\HoneypotBundle\Types;

class HoneypotType extends AbstractType
{
    public const CODE = 'SPAM';
    
    // ...
    
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email', EmailType::class,
                [
                    'required' => true,
                    'constraints' => [
                        new Assert\NotNull(),
                        new Assert\Email(null, "The email '{{ value }}' is not a valid email.", "strict")
                    ]
                ])
            ->add('fakeEmail', EmailType::class,
                [
                    'label_attr' => [
                        'style' => 'font-size: 0;'
                    ],
                    'attr' => [
                        'style' => 'height: 0; width: 0; margin: 0; padding: 0; text-decoration: none; border: none;'
                    ],
                    'row_attr' => [
                        'style' => 'margin: 0 !important; height: 0; width: 0; color:transparent;'
                    ],
                    'constraints' => new Assert\Callback([$this, 'manageSpam']),
                    'required' => false,
                ]);
    }

    public function manageSpam($data, ExecutionContextInterface $context): void
    {
        $form = $context->getRoot();
        $fakeEmail = ($form->get("honeypot"))->get('fakeEmail')->getData();

        /** @var Request $request */
        $request = $this->requestStack->getCurrentRequest();

        $ip = $request->getClientIp();

        if ($fakeEmail) {

            if ($this->logger) {
                $this->logger->alert("Spam tried with $fakeEmail from $ip");
            }

            $context
                ->buildViolation("Spam tried with $fakeEmail from $ip")
                ->setCode(self::CODE)
                ->setParameters(['email' => $fakeEmail, 'ip' => $ip])
                ->addViolation();
        }
    }
    
    // ...
}

Usage

First you have to add the `use HoneypotableTrait` in your entity class containing the contact form data.

class Message
{
    use HoneypotableTrait;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;
    
    // ...
}

Then you will need to create a new migration and run it with :

php bin/console make:mig && php bin/console d:m:m -n

Once the migration is done, you can add the HoneypotType field in your contact form

# src/Form/MessageType

class MessageType extends AbstractType
{
    // ...
    
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('firstname', TextType::class)
            ->add('lastname', TextType::class)
            ->add('honeypot', HoneypotType::class)
            ->add('save', SubmitType::class, ['label' => 'Send Message']);
    }
    
    // ...
}

Then you can get the honeypot fields in the twig template containing the contact form fields like this :

// ...

{% block body %}

{{ form_start(form) }}

// ...

{{ form_row(form.honeypot.email) }}
{{ form_row(form.honeypot.fakeEmail) }}

// ...

{{ form_end(form) }}

{% endblock %}

On form submit, if HoneypotType's `manageSpam function detects a spam, it builds and adds a violation to the form's hidden fakeEmail field, so at this point $form->isValid()` will return false.

This violation contains an array of parameters that you can use if you need to.

[
    'email' => 'honeypot@test.com', 
    'ip' => '127.0.0.1'
]
$errors = $form->getErrors(true);
$spamDetected = $errors->findByCodes(HoneypotType::CODE);

if (count($spamDetected) !== 0) {
    $spamData = $spamDetected->current()->getMessageParameters();
}

Subscriber

When HoneypotType's manageSpam function detects a spam it also dispatches a `HoneypotSpamDetectedEvent` that contains the email and ip of the spammer. You can listen to it by creating a subscriber.

<?php

namespace App\EventSubscriber;

use Ervin11\HoneypotBundle\Event\HoneypotSpamDetectedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class HoneypotSpamSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            HoneypotSpamDetectedEvent::NAME => 'onSpamDetected',
        ];
    }

    public function onSpamDetected(HoneypotSpamDetectedEvent $event)
    {
      // ... Do something
    }
}

Logging

If you have a monolog default main channel in dev mode, it will log the email and the IP address in /var/log/dev.log