Installation:
composer require digivia/form-handler
Ensure your project uses Symfony 5.4+ and PHP 8.
Enable the Bundle:
Register Digivia\FormHandler\DigiviaFormHandlerBundle in config/bundles.php (Symfony 5.1+).
For older Symfony versions, add it to AppKernel.php.
First Use Case:
Create a form handler class (e.g., src/Form/Handler/ContactFormHandler.php):
namespace App\Form\Handler;
use Digivia\FormHandler\FormHandlerInterface;
use Symfony\Component\Form\FormInterface;
class ContactFormHandler implements FormHandlerInterface
{
public function handle(FormInterface $form): void
{
$data = $form->getData();
// Process form data (e.g., save to DB, send email)
}
}
Controller Integration: Inject the handler into your controller and delegate form processing:
use App\Form\Handler\ContactFormHandler;
use Digivia\FormHandler\FormHandlerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ContactController
{
public function submit(Request $request, FormHandlerInterface $handler): Response
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$handler->handle($form); // Delegate logic
return new Response('Success!');
}
return $this->render('contact/form.html.twig', ['form' => $form->createView()]);
}
}
Separation of Concerns:
// Controller (thin)
public function submit(Request $request, UserRegistrationHandler $handler): Response
{
$form = $this->createForm(UserRegistrationType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$handler->handle($form); // Pure logic
}
// ...
}
Reusable Handlers:
EmailNotificationHandler, DatabaseSaverHandler).class OrderConfirmationHandler implements FormHandlerInterface
{
private EmailNotificationHandler $emailHandler;
private DatabaseSaverHandler $dbHandler;
public function __construct(EmailNotificationHandler $emailHandler, DatabaseSaverHandler $dbHandler)
{
$this->emailHandler = $emailHandler;
$this->dbHandler = $dbHandler;
}
public function handle(FormInterface $form): void
{
$this->dbHandler->save($form->getData());
$this->emailHandler->sendConfirmation($form->getData());
}
}
Dependency Injection:
EntityManager, Mailer) into handlers:
class UserProfileHandler implements FormHandlerInterface
{
public function __construct(private EntityManagerInterface $em) {}
public function handle(FormInterface $form): void
{
$user = $form->getData();
$this->em->persist($user);
$this->em->flush();
}
}
Form Events:
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class AnalyticsHandler implements FormHandlerInterface
{
public function __construct(private EventDispatcherInterface $dispatcher) {}
public function handle(FormInterface $form): void
{
$this->dispatcher->dispatch(new FormSubmittedEvent($form->getData()));
}
}
Validation:
class SubscriptionHandler implements FormHandlerInterface
{
public function __construct(private ValidatorInterface $validator) {}
public function handle(FormInterface $form): void
{
$errors = $this->validator->validate($form->getData());
if (count($errors) > 0) {
throw new \RuntimeException('Validation failed');
}
// Proceed
}
}
Handler Lifecycle:
// ❌ Avoid: Storing data in handler instance
class BadHandler implements FormHandlerInterface {
private $counter = 0;
public function handle(FormInterface $form): void {
$this->counter++; // Unpredictable behavior!
}
}
Form Submission Check:
$form->isSubmitted() && $form->isValid() in controllers before calling handlers. Skipping this may lead to unintended handler execution (e.g., on page load).// ❌ Risky: Handler called even if form isn't submitted
$handler->handle($form); // May fail or cause side effects
Circular Dependencies:
// ❌ Circular dependency
class HandlerA implements FormHandlerInterface {
public function __construct(private HandlerB $handlerB) {}
}
class HandlerB implements FormHandlerInterface {
public function __construct(private HandlerA $handlerA) {} // ❌
}
Error Handling:
Problem details).// ✅ Preferred: Return boolean or result object
public function handle(FormInterface $form): bool {
if (!$this->isValid($form->getData())) {
return false;
}
// ...
return true;
}
Symfony 6+ Compatibility:
AppKernel deprecation).config/bundles.php is used instead of AppKernel.php.Handler Execution:
use Psr\Log\LoggerInterface;
class DebugHandler implements FormHandlerInterface {
public function __construct(private LoggerInterface $logger) {}
public function handle(FormInterface $form): void {
$this->logger->info('Handler called with data:', ['data' => $form->getData()]);
}
}
Form Data Inspection:
use Symfony\Component\VarDumper\VarDumper;
public function handle(FormInterface $form): void {
VarDumper::dump($form->getData());
}
Dependency Injection Issues:
debug:container command to inspect service availability:
php bin/console debug:container Digivia\FormHandler
Custom Handler Interfaces:
FormHandlerInterface for domain-specific contracts:
interface OrderHandlerInterface extends FormHandlerInterface {
public function handle(Order $order): void;
}
Middleware for Handlers:
class LoggingHandlerDecorator implements FormHandlerInterface {
public function __construct(private FormHandlerInterface $handler, private LoggerInterface $logger) {}
public function handle(FormInterface $form): void {
$this->logger->info('Before handler');
$this->handler->handle($form);
$this->logger->info('After handler');
}
}
Event-Driven Handlers:
EventDispatcher:
class EventDispatchingHandler implements FormHandlerInterface {
public function __construct(private EventDispatcherInterface $dispatcher, private FormHandlerInterface $handler) {}
public function handle(FormInterface $form): void {
$this->dispatcher->dispatch(new FormHandlingEvent($form, FormHandlingEvent::PRE_HANDLE));
$this->handler->handle($form);
$this->dispatcher->dispatch(new FormHandlingEvent($form, FormHandlingEvent::POST_HANDLE));
}
How can I help you explore Laravel packages today?