mindy/form-bundle
Mindy Form Bundle is a PHP package that packages form-building utilities and helpers, making it easier to define, render, and manage forms with less boilerplate. It aims to streamline common form workflows and provide a simple integration point for PHP applications.
mindy/form-bundle is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no config/form.php, no Blade views, no illuminate/* dependency. It wires symfony/form into the MindyPHP container. As a Laravel developer, "getting started" means first deciding whether you actually want to run Symfony's Form component inside Laravel at all — almost certainly you don't, but here's what a real engagement looks like.
Do this before touching the package:
vendor/mindy/form-bundle/composer.json. This is the only reliable contract. Check:
symfony/form, symfony/options-resolver, symfony/validator version constraints.mindy/* runtime dependency. If it does, it is not standalone and the answer is "no."*Bundle.php) and any DependencyInjection/*Extension.php. That tells you the service IDs, aliases, and whether there is a form.factory service you can pull out of the container.Resources/config/ for services.yml/services.xml and Resources/views/ for Twig templates. If templates are Twig-only, you are committing your rendering layer to Twig alongside Blade — a tax most teams won't pay.Minimal install (spike only — isolated branch, never on main):
composer require mindy/form-bundle
Then, in a Laravel context, the "bundle registration" does not exist. You would need to:
// config/app.php — providers[]
Mindy\FormBundle\FormBundle::class, // will not autoload as an SP; needs a bridge
Symfony bundles are registered via Kernel::registerBundles(), which Laravel has no equivalent for. In practice you would write a small Laravel service provider yourself that:
// app/Providers/SymfonyFormBridgeProvider.php
$this->app->singleton('form.factory', function ($app) {
$csrf = new CsrfTokenManager(/* Symfony CSRF storage, not Laravel's */);
return Forms::createFormFactoryBuilder()
->addExtension(new ValidatorExtension($app['validator']->getSymfonyValidator()))
->getFormFactory();
});
That's the reality: you are writing the adapter. If that paragraph made you uncomfortable, stop here — the answer for a Laravel app is Form Requests + Blade/Livewire/Filament.
First real use case, if you proceed: a create/edit form mapped to an Eloquent model, using Symfony's AbstractType with a custom DataMapper that maps to/from your model. This is where Symfony Form shines (declarative field definitions with data_class mapping) and where the bundle would help if it existed in your stack. It does not, so you recreate it.
Recommended first step for a Laravel team: don't start with the package. Start with the problem it claims to solve. Open a Laravel Form Request, a Blade component, and a Filament resource side-by-side, and confirm the 80–95% overlap. Only proceed to the package if you find a concrete gap those three can't close (and dynamic server-driven form schemas are the only plausible candidate — for which symfony/form standalone, or laravel-livewire/forms, or a JSON-schema renderer are better picks).
Because the package is a thin registration layer over symfony/form, the patterns you'd actually write are Symfony Form patterns, plus the integration glue you own. Below are the patterns worth knowing if you adopt this — and each one carries a note about the Laravel-native equivalent that is almost always cheaper.
1. Form type as a class, one per entity operation
The canonical Symfony pattern is an AbstractType per form. This is genuinely nice for reuse and testability.
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $b, array $o): void
{
$b->add('title', TextType::class)
->add('body', TextareaType::class)
->add('publishedAt', DateTimeType::class, ['required' => false]);
}
public function configureOptions(OptionsResolver $r): void
{
$r->setDefaults(['data_class' => Post::class]);
}
}
Laravel equivalent: a Form Request for validation plus a Blade component or Livewire form object for fields. You get fewer lines dedicated to plumbing (data_class mapping is free with Eloquent).
2. Data mappers for entity binding
Symfony Form will take an object, bind it via data_class, and write back on submit. If you use this with Eloquent you need a DataMapperInterface implementation — Symfony's default PropertyPathMapper uses Symfony's PropertyAccessor, which does not understand Eloquent's magic __get/__set or accessors/mutators reliably. You will write a mapper. This is the single biggest integration cost.
Alternative: ->map() calls to Eloquent manually in your controller. At that point Symfony Form has lost its main advantage.
3. Validation via the Symfony Validator, not Laravel's
Symfony Form integrates with symfony/validator through ValidatorExtension. Laravel's Validator is a different component with different rule syntax, different constraint classes, and a different error bag. If you bridge them, you either:
Validator for form handling entirely (breaks every other Laravel convention in the app), orNone of these are good. This is the pattern that most reliably kills the adoption.
4. Rendering: Twig vs Blade
The bundle ships Twig helpers. In a Laravel app you have Blade. Options:
twig/twig + a view renderer integration). Technically possible; operationally painful — two template engines, two escaping models, two component ecosystems.FormView is a tree of FormView objects; you can walk it from Blade. You are then reimplementing Symfony's form theme system. Expect 1–2 weeks for parity on a modest form.FormView::createView() at all. At which point the rendering half of the package is unused.5. CSRF integration
The bundle's CSRF default is Symfony's CsrfTokenManager, backed by Symfony's session/token storage. Laravel has its own CSRF middleware. You have to pick one and disable the other on routes where the form appears, or wire a Laravel-backed token storage adapter into Symfony's manager. Easy to get wrong; produces confusing 419s (Laravel) or "Invalid CSRF token" (Symfony) depending on which wins.
6. File uploads
Symfony uses FileType with UploadedFile from symfony/http-foundation. Laravel's Illuminate\Http\UploadedFile extends Symfony\Component\HttpFoundation\File\UploadedFile, so this actually mostly works — but Request::file() in Laravel returns the Illuminate subclass, and Symfony's form handling expects $request->files populated. You must convert: $form->handleRequest($symfonyRequest) where symfonyRequest is built from Laravel's request, or use a converter. Small but real.
7. A wrapper interface (the only pattern I'd recommend regardless of decision)
If you decide to use any of this, wrap it:
interface FormFactory
{
public function create(string $type, mixed $data = null): FormView;
public function handle(FormView $form, Request $r): FormResult;
}
One or two call sites at the boundary means the day you rip out Symfony Form (which will come) is a one-file change, not a rewrite.
8. Testing strategy
Test AbstractType classes in isolation with FormFactoryBuilder::create() from symfony/form test utilities. Do not test the bundle's DI wiring — there is almost certainly none tested upstream (this is a 1-star package; assume zero coverage and read the source). Budget time for an internal test suite around the bridge and the data mapper. That suite is your defense against Symfony upgrades breaking the integration.
The package is not for Laravel — this is the #1 gotcha. Everything below is downstream of that. If your team decides to proceed anyway, these are the things that will bite you.
Dependency resolution will likely fail on a Laravel upgrade day. Laravel pins symfony/* for console, process, http-foundation, http-kernel, etc. The bundle's constraints on symfony/form and symfony/options-resolver may not intersect with Laravel's pins, especially across major versions. Test resolution before committing to a spike:
composer require mindy/form-bundle --dry-run
If it fails, you'd need to fork and relax the constraints yourself — which means you now own the compatibility matrix forever.
No ServiceProvider, no config/ merge, no facades. Any Laravel-native expectation (publishing config, Form:: facade, artisan make commands) does not exist. Everything you need is in the vendor source. Budget a day for reading vendor/mindy/form-bundle/ cold — this is the only documentation you'll get.
CSRF conflict is the most likely silent breakage. If both Laravel's CSRF middleware and Symfony's CsrfExtension are active, either handleRequest() sees a token it doesn't recognize (Symfony error) or Laravel's middleware rejects before the form ever runs (419). Diagnose by logging the token from both sides. Long-term, disable one; short-term, remember which route group uses which.
Data mapping to Eloquent is not automatic. Symfony's data_class + PropertyPathMapper assumes plain PHP objects with public/protected properties accessed via Symfony's PropertyAccessor. Eloquent's magic property access through __get, fillable, casts, and accessors/mutators does not reliably round-trip. Symptom: form populates fine but isValid() returns true with an empty data object, or handleRequest silently drops fields not in fillable. Write an explicit data mapper; do not trust the default.
Twig vs Blade. If you keep Twig for form rendering, you now have two template engines. Twig::escape and Blade's e() differ in edge cases (HTML vs htmlspecialchars default flags), and mixing form theme overrides with Blade components is a debugging nightmare. Prefer to re-render manually if you're in a Blade codebase, but understand that's the majority of the work and negates much of the "reusable Twig helpers" pitch.
Form themes are Symfony's, not yours. form_theme blocks are a distinct concept from Blade components. You cannot reuse a Blade x-input component inside a Symfony form theme without writing a Twig equivalent. Plan for duplicated UI code or a renderer rewrite.
Error messages come back as FormErrorIterator, not Laravel's MessageBag. Controller code must adapt:
// Not: $request->validate(...); $errors->all()
foreach ($form->getErrors(true) as $error) {
// $error->getMessage(), $error->getOrigin()->getName()
}
Easy to leak unrendered errors to the user if you don't walk the iterator recursively (getErrors(true)).
Debugging tips:
dump($form->getConfig()->getOptions()) and dump($form->getViewData()) from a controller/console command reveal what Symfony actually sees vs what your model thinks it has.$form->createView() — an unmapped field, or one that shares a name with a parent form, will be nested. The tree is not flat.symfony/form uses event listeners (PRE_SUBMIT, POST_SUBMIT) internally. If a field disappears only after submit, a PRE_SUBMIT listener changed the structure. Search the bundle's types for addEventListener.FormFactory::create() swallowing an exception and returning a partial form usually means a missing data_class or an unknown field option. Run with forms.validate / debug in your dev env if the bundle wires such a config.Extension points are the bundle's, not yours. The bundle may expose a config tree (Extension::load()), a form type, or a service you can decorate. These are the only officially supported override points. If you need to change behavior in the bundle class itself, you are forking — accept that and pin your fork. MIT is friendly; preserve the copyright header.
Version drift is the failure mode you must plan for. A Symfony minor bump pulled in by a Laravel patch release can orphan the integration. Mitigations, in order of preference:
symfony/form and its transitives in composer.json explicitly and add a conflict rule preventing surprise upgrades.composer update --dry-run weekly and alerts on Form-adjacent changes.The package will almost certainly not be maintained. 1 star, 0 dependents, no visible CI. Treat every line you deploy as your code, with a named owner and a documented fork point. If you don't have that owner, you don't have the capacity to adopt this — and that's the honest answer for a Laravel team.
Better alternatives for form work in Laravel, in rough order of fit:
symfony/form used directly (not via this bundle) with an explicit bridge — only if you genuinely need server-driven dynamic schemas, and even then, a JSON-schema renderer is usually better.The one thing worth stealing. The pattern of "form type as a class, bound to a data class, with a configured options resolver" is a good shape. You can reproduce it in Laravel in ~50 lines: a base FormObject abstract class with a fields(), rules(), and data(): array method, plus a Blade component that renders fields(). That internal abstraction — tested, owned, reversible — is the right lesson from this package, not the dependency itself.
How can I help you explore Laravel packages today?