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 Bundle Laravel Package

neutron/form-bundle

Neutron Form Bundle streamlines form creation and management in PHP applications with reusable form components, validation, and flexible rendering. It simplifies submissions and customization while integrating smoothly with Symfony-style workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the framing. neutron/form-bundle is a Symfony bundle, not a Laravel package. The namespace is Neutron\FormBundle, it boots via Symfony's Bundle/FrameworkBundle mechanism, and it depends on symfony/form, symfony/framework-bundle, and Twig. There is no service provider, no config/form.php, no facade, no vendor:publish assets. composer require neutron/form-bundle will resolve in a Laravel app and then do absolutely nothing.

So for a Laravel developer the honest workflow is: read it as a reference implementation, borrow the pattern, don't adopt the code.

Minimal steps:

  1. Read src/Form/Type/ — that directory is the entire value proposition. One form type per RIA widget (date picker, autocomplete/chosen select, rich editor), each declaring its own validation constraints and the CSS/JS assets it needs.
  2. Read Resources/views/ — this is where each type injects its assets into the rendered form, guarded so they're emitted once. That "asset emission" trick is the reusable part.
  3. Check composer.json — Symfony 2.x-era constraints, likely PHP 5.x. Confirm in a clean container before spending any more time (composer require in a throwaway Laravel 11 app is a one-hour answer that de-risks everything).
  4. Decide: pattern-transfer or abandon. Given 11 stars and 0 dependents, pattern-transfer is almost always the answer.

First legitimate use case, in Laravel terms — replace one hand-rolled JS field with a Blade component that declares its own asset:

{{-- resources/views/components/form/select2.blade.php --}}
@props(['name', 'options' => [], 'selected' => null])

<select name="{{ $name }}" {{ $attributes->merge(['class' => 'select2']) }}>
    @foreach ($options as $value => $label)
        <option value="{{ $value }}" @selected($value == $selected)>{{ $label }}</option>
    @endforeach
</select>

@once
    @push('styles')
        <link rel="stylesheet" href="{{ asset('vendor/select2/select2.min.css') }}">
    @endpush
    @push('scripts')
        <script src="{{ asset('vendor/select2/select2.min.js') }}"></script>
        <script>document.querySelectorAll('.select2').forEach(el => $(el).select2());</script>
    @endpush
@endonce

That's ~20 lines and reproduces the bundle's core idea: the field component owns its widget and its assets, and the layout emits them once.

Implementation Patterns

1. A field-type registry instead of scattered Blade partials.

The bundle's structural win is that a field is a class, not a template fragment. Mirror it:

interface FieldType
{
    public function name(): string;                 // 'select2', 'datepicker'
    public function view(): string;                 // blade component
    public function rules(): array;                 // Laravel validation rules
    public function assets(): array;                // ['js' => [...], 'css' => [...]]
}

Register them in a small service container binding (app/FieldTypes.php or a #[Tag]-style collection). New widget = new class, no edits to existing forms. This is the extension point the bundle actually got right, and it survives the port.

2. Schema-driven form definitions.

The "define fields, validation, and layout once, render anywhere" pitch maps cleanly onto a config array or JSON column:

// config/forms/onboarding.php
return [
    'fields' => [
        ['type' => 'text',   'name' => 'company', 'rules' => ['required', 'max:255']],
        ['type' => 'select2','name' => 'country', 'rules' => ['required', 'in:US,CA,GB']],
        ['type' => 'date',   'name' => 'start_on','rules' => ['nullable', 'date']],
    ],
];

Then build rules programmatically at submit time:

$rules = collect($schema['fields'])->mapWithKeys(fn ($f) => [$f['name'] => $f['rules']])->all();
$data  = Validator::make($request->all(), $rules)->validate();

Keep the schema dumb (arrays/scalars) so it can live in a DB column, a config file, or a JSON API response.

3. Validation parity, translated not copied.

The bundle leans on Symfony's Constraint classes. Don't port them literally — map them:

Symfony Laravel
NotBlank required
Length(min,max) min:n / max:n
Email email
Choice in:a,b,c
Regex regex:/…/

Put the mapping in the field type (rules() returns Laravel rules directly). This is where most of the "free validation" value gets realized.

4. Multi-step / repeatable fields.

  • Multi-step: model each step as a sections key in the schema and render one section per request (session-backed draft, or Alpine tabs if no persistence is needed). Avoid duplicating submit logic per step — one FormController with a step parameter.
  • Repeatable / collection: Livewire (wire:model="rows.{{ $i }}.field") or Alpine x-for. This is the one area where the original bundle's server-rendered approach genuinely struggles; don't fight it, use Livewire.

5. Asset deduplication at the layout level.

Follow the bundle's contract: components push, the layout stacks. In layouts/app.blade.php:

@stack('styles')
...
@stack('scripts')

Keep @once inside each component. If you outgrow this, promote widget JS to entry points in resources/js/app.js and let Vite handle dedupe — but start with @once @push.

6. If you truly must run the bundle.

Only realistic shape: a standalone Symfony app (or Docker service) exposing the rendered forms as HTML/JSON, called from Laravel over HTTP. Treat it as an external system, not a dependency. This is only defensible for legacy preservation, never greenfield.

Gotchas and Tips

  • Silent no-op install. The package will autoload into vendor/neutron/form-bundle and register nothing. Don't chase phantom "configuration" — there is none. If you're not running Symfony, remove it and move on.
  • The client contract is AngularJS 1.x-era. "Zero JavaScript" means the server emits markup/attributes a specific client runtime interprets. That runtime has been EOL since December 2021. This is the single biggest reason not to run the code, and the reason you can't debug it from the PHP side — failures surface only in the browser.
  • @once + @push ordering. @once guarantees the block renders once, but only if the component renders before the layout's @stack('scripts'). Forms rendered inside a modal or a deferred Livewire component will push too late. Fix: render the component in the base view, or hoist required scripts into app.js.
  • CSRF token loss with JS widgets. Any widget that submits via AJAX (autocomplete creating a record, file upload) will bypass Laravel's token unless you explicitly inject it. The classic symptom is a silent 419. Inject @csrf into the widget's own form/meta tag, or read X-CSRF-TOKEN from a meta tag in your fetch wrapper.
  • Validation rule drift. If you edit the schema and forget the field type's rules(), you get a form that renders new fields but validates nothing. Unit-test the schema → rules derivation, not the rendered HTML.
  • Don't port the Symfony Constraint objects. They carry annotations/DI assumptions that won't survive. Translate to Laravel rule strings at the boundary (see table above).
  • The extension point worth stealing. Per-type asset registration (assets() on the field type) is the design decision that makes the pattern maintainable. Keep it as an interface contract; never let a Blade partial hardcode a <script> tag that another partial also includes.
  • License is clean, maintenance is not. MIT means you can fork. But 11 stars, 0 dependents, and a single maintainer mean a fork is 100% yours forever — no upstream to rebase against. If you fork, vendor it under app/Support/Forms, drop the Composer constraints, pin transitive deps, and set a sunset date.
  • Score ≠ artifact. The composite score (~0.88) reflects the pattern's merit, not this package's fitness. Read it as "the idea of server-owned declarative forms is good," then implement that idea on Symfony UX / Livewire, which is the maintained equivalent.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console