isometriks/spam-bundle
Symfony bundle to reduce spam on forms with simple protections like timed submission (min/max seconds between render and submit) and honeypot fields. Easy Composer install, configurable defaults, and per-form options to enable or override settings.
Installation:
composer require isometriks/spam-bundle
For Symfony Flex, no additional steps are needed. For non-Flex projects, add to config/bundles.php:
Isometriks\Bundle\SpamBundle\IsometriksSpamBundle::class => ['all' => true],
First Use Case: Enable timed spam prevention on a form by adding the option in your form type:
$builder->add('name');
// Enable timed spam with default settings (7s min delay)
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$event->getForm()->add('timed_spam', HiddenType::class, [
'mapped' => false,
'data' => true, // Enable timed spam
]);
});
Or via form options when creating the form:
$form = $this->createForm(MyType::class, null, [
'timed_spam' => true,
]);
First Honeypot Use Case:
Add a hidden field to your form template (e.g., contact.html.twig):
<input type="text" name="email_address" class="hidden" />
Enable honeypot in your form type:
$builder->add('honeypot', HiddenType::class, [
'mapped' => false,
'required' => false,
'constraints' => [
new NotBlank(['message' => 'Form fields are invalid']),
],
]);
Or via form options:
$form = $this->createForm(MyType::class, null, [
'honeypot' => true,
'honeypot_field' => 'email_address',
]);
Verify Setup:
Configure spam protection globally in config/packages/isometriks_spam.yaml:
isometriks_spam:
timed:
min: 10 # seconds
max: 3600
global: true # Apply to all forms
message: 'Please wait 10 seconds before submitting.'
honeypot:
field: 'email_address'
use_class: true
hide_class: 'hidden-spam-field'
global: true
message: 'Invalid form submission.'
Override global settings on a per-form basis:
// In your controller
$form = $this->createForm(MyType::class, null, [
'timed_spam' => true,
'timed_spam_min' => 5, // Override global min
'honeypot' => false, // Disable honeypot for this form
]);
Use FormEvents::PRE_SET_DATA to dynamically enable/disable spam protection based on runtime logic (e.g., user role):
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
if ($data && $data->isAdmin()) {
$event->getForm()->add('timed_spam', HiddenType::class, [
'data' => false, // Disable for admins
]);
}
});
}
Enable both timed spam and honeypot on the same form:
$form = $this->createForm(MyType::class, null, [
'timed_spam' => true,
'timed_spam_min' => 7,
'honeypot' => true,
'honeypot_field' => 'phone_number',
]);
hidden-spam-field class:
<input type="text" name="phone_number" class="hidden-spam-field" />
Display form errors in Twig:
{% if form.vars.errors is not empty %}
<div class="alert alert-danger">
{{ form_errors(form) }}
</div>
{% endif %}
isometriks_spam.yaml.min, the form is marked invalid with the configured message.Edge Case Handling:
min: 0 to disable timing checks (but keep the feature for other forms).$form->createView() to reset the timestamp.Bot Detection:
For AJAX-heavy forms (e.g., React/Vue frontends), ensure:
document.querySelectorAll('.hidden-spam-field').forEach(el => {
el.style.display = 'none';
});
Extend the bundle’s behavior by listening to form events:
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomSpamSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SUBMIT => 'onPreSubmit',
];
}
public function onPreSubmit(FormEvent $event)
{
$form = $event->getForm();
if ($form->has('custom_field')) {
// Add custom logic (e.g., rate limiting)
}
}
}
Register the subscriber in your bundle or controller.
Combine with Symfony’s validators for layered protection:
use Symfony\Component\Validator\Constraints as Assert;
$builder->add('email', EmailType::class, [
'constraints' => [
new Assert\NotBlank(),
new Assert\Email(),
],
]);
Log invalid submissions for analytics:
use Psr\Log\LoggerInterface;
class SpamLogger implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return [
FormEvents::SUBMIT => 'onSubmit',
];
}
public function onSubmit(FormEvent $event)
{
if (!$event->getForm()->isValid()) {
$this->logger->warning('Spam attempt detected', [
'form' => $event->getForm()->getName(),
'errors' => $event->getForm()->getErrors(true),
]);
}
}
}
Test spam protection with:
$this->container->get('kernel')->getContainer()->set('isometriks_spam.timed_spam_handler', $
How can I help you explore Laravel packages today?