rezozero/liform-bundle
Symfony bundle that converts Symfony form definitions into JSON Schema, letting front-end apps render forms from a single source of truth. LiFormBundle serializes form types, fields and options into portable JSON, keeping server-side and client-side forms in sync.
Architecture fit — the framework mismatch is the first gate, not a footnote. Despite the "Laravel/PHP package" framing in the intake record, rezozero/liform-bundle is a Symfony bundle with zero Laravel applicability as packaged. It exists to register the liform library (Symfony Form type → JSON Schema converter) into a Symfony kernel: a bundle class, DI service definitions, configuration tree, and Twig form-theme support. Laravel has no equivalent lifecycle — no bundle registration, no Twig, no Symfony container. The only salvageable asset is the underlying liform/liform library, which carries symfony/form as a hard runtime dependency. So "leveraging" this package in a Laravel codebase is not configuration; it is standing up Symfony's Form component as a subsystem to obtain a serialization feature. As a TPM, treat that as the decision boundary: the question is not "should we install this bundle," it is "do we want a second form abstraction and a second validation story in our deployable."
What the underlying capability genuinely delivers. Given a Symfony Form type, liform emits a JSON Schema describing field names, types, and a subset of constraints (required, enum, minLength, pattern, numeric bounds). That has real product value in exactly one scenario: one team owns form definitions server-side, a second team renders them client-side with a schema-driven renderer (react-jsonschema-form, Formily, JSON Forms), and you want to collapse a hand-maintained contract into a single artifact. It also supports API contract and documentation generation, and gives a build-vs-buy signal — this is a thin MIT-licensed adapter, not a platform. Effort to replicate the concept in-house is days to a couple of weeks depending on form-type exoticism.
Where the abstraction leaks — this is the technical risk that decides the business case. Symfony Form is a stateful, event-driven, entity-aware object graph; JSON Schema is static and declarative. Fidelity degrades silently in the cases that matter most:
EntityType/ChoiceType behind a query builder, PRE_SET_DATA/PRE_SUBMIT listeners, and collection types with prototypes have no faithful schema representation.DateTime, ID ↔ entity) mean the schema describes the transformed shape, not the payload the client actually posts.if/then, dependentRequired, $defs). Verify against the chosen renderer before anyone commits.Adoption risk is the headline for a TPM. Zero stars, zero dependents, single-maintainer provenance (an agency publishing a wrapper around Limenius' liform). Not disqualifying for an internal tool, but it means: no community CVE triage, no Stack Overflow corpus, no guarantee of a Symfony 7 / PHP 8.3+ release, and no external users to absorb the cost of discovering sharp edges. A composer audit against a stale transitive tree is a live concern for anything internet-facing.
Key questions to answer before funding engineering time:
composer.json actually constrain, and what is the last-commit date? Does it install cleanly against a security-supported Symfony Form version on our PHP baseline?EntityType + custom constraints means cosmetic schema and a failed spike.Verdict. Low architectural fit for a Laravel codebase, unproven maintenance posture, and a value proposition entirely contingent on a schema-driven frontend that may not exist. Not a default-adopt. Worth a timeboxed spike only if the frontend contract problem is real and recurring.
Stack fit. Poor as packaged, moderate if you extract the library and discard the bundle. The decision is really about whether liform/liform + symfony/form earns a place in a Laravel service — the bundle layer has no Laravel analogue and should be ignored entirely.
Three viable paths, ascending cost:
| Path | What it means | Cost | When it wins |
|---|---|---|---|
| A. Direct library use | composer require liform/liform symfony/form; author Symfony Form types for affected resources; wrap in a Laravel service returning schema JSON |
1–2 sprint-weeks incl. spike | Frontend already consumes JSON Schema; small, static form surface |
| B. Laravel-native adapter | Translate Laravel FormRequest rules or a small schema DSL to JSON Schema; no Symfony |
2–4 sprint-weeks | You want zero cross-framework dependency and full coverage control |
| C. Schema-first | Author JSON Schema / OpenAPI components; validate server-side with opis/json-schema |
~1 sprint-week | Forms are few, or the contract genuinely is the product |
Path B is the right long-term answer for a Laravel shop. Path A is the fastest way to prove the concept's value before paying for B. Fund A as a throwaway spike with an explicit kill criterion.
Migration path (Path A) — greenfield coexistence, not migration. Sequence:
FormSchemaGenerator::for(string $formKey): array. Nothing else in the app may touch Symfony classes. This is the single most important architectural decision: it makes the whole thing swappable for Path B or C at one seam.Forms::createFormFactoryBuilder() in a Laravel singleton with minimal extensions, avoiding the Symfony event dispatcher unless proven necessary.GET /api/schemas/{formKey}) with HTTP caching and a schema version field. Do not embed schemas in Blade views; runtime embedding bypasses caching.Compatibility checks before committing. Pin exact versions and commit composer.lock. Verify three things: the Symfony Form major available for our PHP baseline; liform's JSON Schema draft level against the chosen renderer; and whether the dependency tree's PHPUnit / PHP-CS-Fixer / PSR constraints conflict with the app's. If the package forces a Symfony Form version out of security support, that alone kills Path A.
Sequencing against delivery. Do not put this on a critical path. Run it as a parallel spike alongside the admin-surface build. Gate broader rollout on two things: the spike showing ≥80% constraint coverage on our real forms, and the frontend team confirming the schema is sufficient to render including error states — JSON Schema carries no server-side validation messages, so error UX must be solved separately.
Maintenance. The dominant cost, and it is not the code you write — it is the dependency tree you inherit. You will maintain a Symfony Form factory inside Laravel, a mapping layer whose fidelity requires continuous verification, and a third-party package with no visible maintenance. Realistic budget: 0.1–0.2 FTE ongoing for patching and drift, spiking sharply on any PHP major upgrade or a Symfony Form security release upstream has not absorbed. If upstream goes quiet, you fork; budget that explicitly rather than discovering it at upgrade time.
Support. Effectively zero external support — no maintainer community to escalate to, no issue-tracker velocity to read, no downstream users who have already hit your bug. The real hazard is internal knowledge concentration: one engineer understands the Symfony Form → JSON Schema translation, and when they leave the module becomes a black box. Mitigate with the golden-file tests, written design notes on the mapping decisions, and deliberate pairing.
Scaling. Scales fine on throughput — schema generation is a cacheable, build-time-ish operation. It does not scale on breadth: every new form type, custom constraint, or dynamic field adds mapping work, and coverage degrades quietly as forms grow more stateful. Plan for the schema catalogue to be a curated subset, not "all forms."
Failure modes to monitor.
Ramp-up. Roughly one to two weeks for a mid-level Laravel engineer unfamiliar with Symfony Form, because the conceptual overhead (form types, extensions, data transformers, event subscribers) is not transferable from Laravel knowledge. The JSON Schema side is easier — a day or two. Budget a spike plus a written handover artifact; do not leave this undocumented and single-owner.
Recommendation for the roadmap. Do not adopt the bundle. Run a one-to-two sprint spike on the pattern (Path A), with a pre-agreed kill criterion of <80% constraint coverage on real forms. If the spike passes and a frontend consumer is confirmed, migrate immediately to Path B (Laravel-native adapter) rather than shipping Path A to production — the adapter is a few weeks of work and permanently removes the cross-framework dependency, the unmaintained package, and the security-patching exposure. If the spike fails, take Path C and move on.
How can I help you explore Laravel packages today?