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

Ezforms Bundle Laravel Package

heliopsis/ezforms-bundle

Heliopsis EzForms Bundle is a Symfony bundle for building and managing dynamic forms with configurable fields and reusable form definitions. It helps integrate customizable forms into Symfony applications with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before line one. heliopsis/ezforms-bundle is not a Laravel package. It is a Symfony bundle built for the eZ Publish 5.x era (legacy kernel bridge, Symfony 2.x, PHP 5.x-era code, GPL-2.0, 11 stars, 0 dependents). There is no service provider, no auto-discovery, no config/ezforms.php, no Facade. Composer will happily download it into a Laravel app and nothing will happen — worse, its require block will likely fight your PHP/Symfony constraints and fail resolution.

So "day-to-day" splits into two honest paths. Pick one on day one.

Path A — you actually run eZ Publish/Symfony (legacy maintenance). Then it's a normal bundle install:

composer require heliopsis/ezforms-bundle
// app/AppKernel.php
new Heliopsis\EzFormsBundle\HeliopsisEzFormsBundle(),

Look here first, in this order — the repo has no real docs, so the code is the documentation:

  1. DependencyInjection/Configuration.php — the complete config tree. Every key you can set lives here; treat it as the manual.
  2. Resources/config/services.yml — service IDs and tags used to discover form types and handlers. These are your extension points.
  3. Form/, plus any Handler/ or Mapper/ directory — how a submitted form is turned into something eZ Publish understands.
  4. Controller/ and Resources/views/ — how the form is injected into a content view.

Path B — you are a Laravel shop (the normal case). Don't install it. Read it as a reference design and rebuild the pattern natively. The bundle's whole idea is one line: a Symfony form type is bound to a content class, and submissions are handled by a service. That maps cleanly onto Laravel primitives:

Bundle concept Laravel equivalent
AbstractType bound to content class FormRequest + a FormDefinition model / config array
Content class → form type mapping config('forms.bindings') keyed by content type slug
Handler service (autowired) Action class or queued Job invoked from the controller
Content view rendering the form Blade component <x-ez-form :definition="$definition" />
Persisted submission as content form_submissions table + FormSubmission model

First use case either way: a contact form bound to a content type. Path A: define ContactType extends AbstractType, bind it to the contact_page content class in config, create a content object in the admin, clear cache, view it, submit it, confirm the handler fires (mail/redirect/publish). Path B: model a FormDefinition (slug, fields JSON, handler class), resolve it from the page's content type, render via a Blade component, POST to a controller that validates with a FormRequest and dispatches the handler.

Get one vertical slice working end to end before anything else. Everything else is variations.

Implementation Patterns

Keep form types/definitions dumb; put content interaction in the handler. In the bundle, don't inject eZ Publish repositories into the AbstractType — it makes the type untestable. Same discipline in Laravel: FormRequest handles validation only, the controller does lookup, the Action does side effects.

One binding per content type, not per content object. The bundle binds at class level, so every contact_page gets the same form. Mirror that in Laravel — it's the sane default. For per-page variation (a "recipient email" for this specific page), store it as a content attribute and read it in the handler:

final class SendContactForm
{
    public function __construct(private Mailer $mailer) {}

    public function handle(FormDefinition $def, array $data, ContentPage $page): void
    {
        $this->mailer->to($page->attribute('recipient_email'))
            ->send(new ContactFormMail($data));
    }
}

Two submission strategies — choose deliberately. The bundle supports both, and so should you:

  • Ephemeral — validate → send mail / call API → redirect with flash. No schema, no admin UI. Start here.
  • Persisted — write a form_submissions row (or, in the bundle, publish a "form submission" content object via eZContentFunctions::createAndPublishObject). Persisted-as-content is the reason eZ Publish shops tolerate this thing — editors browse submissions in the admin — but it couples you to the legacy API and permission model. In Laravel, a table plus a Filament/Nova resource gets you the same editor experience with far less pain.

Render the form in one place. The bundle's value is that CSRF, validation errors, and re-population after a failed submit are handled by Symfony's form renderer in a single content template override. Don't scatter {!! form_widget(...) !!} (or @error blocks) across content templates. In Laravel, put the entire form — CSRF, old input, error bags, success state — inside one Blade component, and have the page render <x-ez-form>.

If you must bridge (eZ Publish stays, Laravel is the front end): isolate the bundle behind a narrow JSON contract. Symfony sidecar owns content lookup, form definition, and submission persist; Laravel owns auth, routing, and UI.

GET  /api/forms/{contentId}   -> { fields: [...], action: "...", csrf: "..." }
POST /api/forms/{contentId}   -> 201 { submissionId } | 422 { errors }

Laravel calls it from a Http client with ->acceptJson(), maps 422 payloads back into the ValidationException, and never imports the bundle directly. This keeps GPL-2.0 at arm's length (network boundary, not linking) — but confirm that with counsel, not with me.

Tag your handlers, don't wire them by hand. The bundle uses Symfony service tags to discover form types and handlers. Laravel's analogue is a registry: bind handler class names in config/forms.php and resolve them via app($def->handler) — this keeps the "add a new form by config" ergonomics without a compiler pass.

Gotchas and Tips

It will not install into Laravel. Don't try. No laravel/framework in require, Symfony 2.x constraints, PHP 5.x-era syntax. composer require in a modern Laravel app either fails resolution or installs an unusable vendor/ entry. Budget a 30-minute spike to prove this, then move on.

GPL-2.0 is a distribution decision, not a dev decision. Linking or shipping it inside a proprietary/SaaS product is a legal question. The sidecar/HTTP boundary is the usual mitigation; get sign-off before you architect around it.

The config file is the documentation. There's no README worth the name. When something won't bind, open DependencyInjection/Configuration.php first — 90% of "why isn't my form showing up" is a key that doesn't exist or is nested one level differently than you guessed.

Binding is class-level, cached, and silent when wrong. Get the content class identifier exactly right (contact_page, not ContactPage). Wrong identifier = no form, no error. Clear cache after every config change — the legacy stack caches aggressively. Debug by dumping the resolved binding in the controller, not by re-reading config.

Siteaccess and translations are the classic trap. eZ Publish content is siteaccess-scoped and translatable; a form bound in one siteaccess may resolve differently (or not at all) in another, and translated content objects can point at a different form type. Test the form on the siteaccess the editors actually use, with the site access matcher active, not on your dev hostname alias. Same for language — submission errors rendered in the wrong locale is a common first-week bug.

CSRF and session mismatch is the #1 bridge failure. If you run Laravel in front of a Symfony sidecar, sessions and CSRF tokens are not shared by default. Either proxy the sidecar under the same cookie domain, or fetch the form + token from the API and post it back untouched. Symptom: 419-style rejections on every submit from the browser but success from curl.

No upstream, so fork it. 11 stars, one maintainer, no security process. If you adopt, vendor the fork internally, pin the commit, and name an owner. That's a line item in the estimate, not a footnote.

Extension points are the service tags. Need custom behavior on submit? Wrap or decorate the handler service rather than editing bundle code — that survives an upstream rebase (if one ever comes). In a Laravel rebuild, the equivalent discipline is an interface (FormHandler) plus container bindings, so you can swap behavior per definition without forking your own app.

Test the form type in isolation. Symfony's TypeTestCase (or a plain Laravel unit test asserting the FormRequest rules) is the only fast feedback loop here. End-to-end against a live eZ Publish instance is slow and flaky; make the handler the thing you integration-test, and unit-test the field definitions.

When to bail: if the requirement grows past "attach a form to a content page" — conditional logic, multi-step, payments, webhooks, spam protection — stop. This bundle doesn't do that, and a week of Laravel-native build (form builder package + policy + Blade components) beats a bridge you have to maintain forever.

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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json