Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Form Handler Bundle Laravel Package

hostnet/form-handler-bundle

Symfony bundle that adds reusable form handler services to separate form processing from controllers. Define handlers for validation, persistence, and success/failure flow, keeping controller actions thin and form logic testable.

View on GitHub
Deep Wiki
Context7

Getting Started

hostnet/form-handler-bundle is a Symfony bundle (not Laravel) that collapses the isSubmitted() && isValid() boilerplate into a single controller call. If you're a Laravel developer, the value proposition maps to a Form Request + controller action, but the implementation is Symfony Form-component-specific — it won't drop into an Eloquent/FormRequest codebase without the Symfony Form component and HttpKernel in place. Treat it as relevant when you're working in (or consulting on) a Symfony app.

Install and register (Flex handles this automatically):

composer require hostnet/form-handler-bundle
// config/bundles.php
Hostnet\Bundle\FormHandlerBundle\FormHandlerBundle::class => ['all' => true],

Where to look first:

  1. The README — the API surface is tiny (roughly one trait + one service), so a 5-minute read covers it.
  2. src/ — the trait you mix into controllers and the handler service it delegates to. That's the entire contract.
  3. Tests/ — this is where you'll find the actual behavior for edge cases (non-submitted form, invalid form, callback return types) since docs rarely cover them.

First use case — the contact/CRUD create form you'd otherwise write by hand:

use Hostnet\Bundle\FormHandlerBundle\Controller\FormHandlerControllerTrait;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class ContactController extends AbstractController
{
    use FormHandlerControllerTrait;

    #[Route('/contact', name: 'contact', methods: ['GET', 'POST'])]
    public function contact(Request $request): Response
    {
        $form = $this->createForm(ContactType::class, new ContactDto());

        return $this->handleForm($form, $request, function (ContactDto $data) {
            // Only reached on a submitted + valid form.
            $this->addFlash('success', 'Thanks, we will be in touch.');

            return $this->redirectToRoute('contact');
        });
    }
}

The callback is the "success path"; if the form is unsubmitted or invalid, the trait falls through and lets you render the form. (Exact method names/signatures vary slightly between versions — confirm against the README handleForm/handler signature before copying.)

Implementation Patterns

Post/Redirect/Get as the default. Always return a redirect from the success callback. Rendering the same template with a success flash after a POST means F5 re-submits and sends duplicate emails/creates duplicate rows. The trait is designed around this flow; lean into it.

Create + edit in one action. The handler doesn't care where the entity came from, so a shared controller works cleanly:

#[Route('/posts/new', name: 'post_new', methods: ['GET','POST'])]
#[Route('/posts/{id}/edit', name: 'post_edit', methods: ['GET','POST'])]
public function edit(?Post $post, Request $request): Response
{
    $post ??= new Post();
    $form = $this->createForm(PostType::class, $post);

    return $this->handleForm($form, $request, function (Post $post) {
        $this->em->flush();

        return $this->redirectToRoute('post_show', ['id' => $post->getId()]);
    });
}

Keep the callback thin — delegate to a service. The bundle buys you the dispatch logic; it doesn't buy you good separation. Inject a PostCreator / command-bus handler and call it inside the closure. This makes the "what happens on success" logic unit-testable without spinning up the kernel.

JSON/AJAX endpoints. Because the callback returns a Response, you can short-circuit the PRG pattern for XHR:

return $this->handleForm($form, $request, fn (ContactDto $dto) => $this->json(['ok' => true]));

Pair this with a check on $request->isXmlHttpRequest() if the same URL serves both HTML and JSON.

Reusable base controller. If you have 20+ actions, wrap the trait in AbstractAppController and add helpers (handleFormAndFlash, handleFormJson). This is the sane extension point — don't fork the bundle for it.

Validation groups and form options. Anything you'd pass to $form->submit() you still control via createForm(..., ['validation_groups' => ...]). The handler just calls handleRequest; it doesn't override your options. Don't expect it to.

Testing. Functional tests with KernelBrowser ($client->submitForm('Save', [...]), assert redirect + flash) cover the whole pipeline including CSRF. For faster tests, extract the callback body into a service and test that directly — the handler itself is the bundle's responsibility, not yours.

Gotchas and Tips

  • It's Symfony-only. No Laravel FormRequest integration, no Eloquent, no Blade. If you're evaluating this "for a Laravel project," stop — you'd need the Symfony Form component, which is a different framework's architecture.
  • handleRequest semantics still apply. HTTP method, CSRF token, and _method spoofing are all Symfony's. If your form uses PUT/DELETE, the form must have method => 'PUT' and the template form_start(form) will render the hidden _method — the bundle won't infer it for you.
  • Callback return type matters. Returning null or an array from the success callback will produce a Response-shaped hole in your controller return type at runtime. Return a Response, always.
  • GET requests must fall through. On a plain GET the form is never submitted, so the callback never fires and the trait returns whatever the fallback path is. Verify this in Tests/ — if your version requires an explicit fallback response, silently returning null from a controller action is a classic 500.
  • Validation is not magic. The handler dispatches; it doesn't add constraints, groups, or business rules. $form->isValid() failing lands you on the fallback path with form_errors(form) in Twig — if you're not rendering those, invalid submissions look like no-ops.
  • Small ecosystem. 71 stars, zero known dependents. That means: no Stack Overflow answers, no blog posts, and you are effectively the QA department. Pin the exact version ("hostnet/form-handler-bundle": "1.0.*" or whatever the release line is), and budget an hour to read src/ before relying on it in a revenue path. The trade-off is a very small, auditable surface — MIT licensed, so a fork is cheap.
  • Service wiring quirks. If handleForm is unavailable or autowiring fails after a Composer update, check that config/services.yaml isn't excluding the bundle's namespace and that the trait is used (not extended). Bundle registration order rarely matters here, but Flex sometimes misses new bundles on upgrade — verify bundles.php.
  • Debugging tips. Set a breakpoint or dd() inside the callback — if it never hits on a POST, the failure is upstream of the bundle: CSRF (csrf_token_id mismatch), wrong HTTP method in the route, or the form name not matching the request payload. Use the Symfony profiler's Forms panel to confirm isSubmitted/isValid without touching code.
  • Extension points worth knowing: the trait (compose it), the handler service (decorate it if you need global logging/metrics around success), and Symfony's FormEvents (use POST_SUBMIT for cross-cutting concerns rather than stuffing the callback). Don't monkey-patch the bundle; decorate.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle