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

Liform Bundle Laravel Package

webdevcraft/liform-bundle

Symfony bundle that turns JSON Schema into native Symfony forms via LiForm. Define form structure in JSON Schema and let the bundle build, render, and validate forms automatically, reducing boilerplate for schema-driven or API-defined forms.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require webdevcraft/liform-bundle

The bundle wraps liform/liform, which converts a built Symfony\Component\Form\FormInterface into a JSON Schema document. It is not a Flex recipe, so you must register it manually in config/bundles.php:

WebdevCraft\LiformBundle\LiformBundle::class => ['all' => true],

Where to look first

  1. Resources/config/services.xml (or config/services.php) in the bundle — confirm the service ID it registers for Liform\Resolver (usually public as liform.resolver). Everything else hinges on this one service.
  2. The upstream liform/liform README and the Liform\Transformer\* classes — that is where 90% of behaviour lives; the bundle is essentially DI glue.
  3. DependencyInjection/*Extension.php — check what (if anything) is configurable and whether it references legacy %...class% parameters.

First use case

You have a Symfony form (say RegistrationType) and a JS SPA that should render it dynamically. Expose the schema:

public function schema(FormFactoryInterface $forms, Resolver $resolver): JsonResponse
{
    $form = $forms->create(RegistrationType::class); // build, do NOT handleRequest()
    return $this->json($resolver->resolve($form));
}

Fetch /api/schema/registration and feed the result to a JSON-Schema form renderer (react-jsonschema-form, vue-form-json-schema, @jsonforms/core). Submission still goes to your normal Symfony form endpoint — Liform only replaces the rendering half of the loop.

Implementation Patterns

One schema endpoint, keyed by form alias. Avoid a controller per form. Keep a whitelist map of alias => FormType::class and resolve by alias; this also gives you one place to enforce authorization and caching.

Treat the form type as the single source of truth. Constraints (NotBlank, Length, Regex, Choice) are what make the schema useful for client-side validation. Never hand-write validation rules in JS that duplicate constraints — if the schema can't express something, add a FormTypeExtension.

Symfony form type extensions as the schema metadata layer. Help text becomes description, attr becomes custom keywords, label becomes title. This is the cleanest place for annotation work because it applies everywhere the form is used:

$builder->add('email', null, [
    'help' => 'We only use this for receipts.',   // -> description
    'attr' => ['placeholder' => '[email protected]'],
]);

Cache the output. A schema is deterministic for a given form definition + locale (labels are translated at resolve time). Cache with a key of form alias + locale + build hash; a deploy that changes a form type naturally busts the hash. Resolving dozens of forms on every request is measurable.

Snapshot-test the schemas. In a functional test, resolve each whitelisted form and compare against a committed .json fixture. This is the cheapest guard against a developer silently changing a public API contract by adding/renaming a form field.

Custom transformers when a field type misbehaves. Liform\Resolver accepts transformers with priorities; reach for one when (e.g.) a custom DataType needs a proper type/format instead of falling back to a permissive {}.

Gotchas and Tips

  • Maintenance risk is the headline. Last release February 2021, zero dependents, zero stars. Treat it as frozen. The pragmatic move for a real project is often to skip the bundle and register the upstream service yourself — the bundle is a thin wrapper, so ~10 lines of services.yaml replicating liform.resolver buys you upgrade freedom and a supported dependency.
  • Composer will likely fight you on Symfony 6/7. The 2021 constraints may cap symfony/form. Expect --ignore-platform-reqs or a fork/repositories vcs entry; verify the form API it uses hasn't shifted.
  • Legacy DI parameters. Check the service XML for %form.factory.class%-style arguments — those parameters were removed in Symfony 5.x and will fatal at container compile, not at runtime.
  • Resolve the created form, not a handled one. $resolver->resolve() on a form that's been submitted/validated produces noise (validation state, submitted data). Build it and resolve it; the schema is about structure, not values.
  • CSRF and submit buttons leak into properties. _token and your SubmitType field usually show up. Strip them in a post-processing step or via a transformer; clients shouldn't see _token.
  • Collections are the weak spot. CollectionType::allow_add yields array schemas whose items and additionalProperties semantics are easy to get wrong, and prototype fields can surface as 0, 1. Always eyeball a collection schema before shipping.
  • Data transformers are invisible. A DataTransformerInterface (entity ID ↔ object, money strings) is not modelled — the schema describes the underlying type. Document mismatches; don't assume the schema describes what the user actually types.
  • Dynamic choice loaders degrade silently. Entity/choice_loader-backed ChoiceType may produce an empty or absent enum because resolving would hit the database. Prefer static choices or a separate /options endpoint for large lists.
  • Localisation is baked into the output. Resolve uses the translator, so cache per locale or you'll serve French labels to English users.
  • Do not expose schemas unauthenticated. They disclose field names, constraints, choice values, and sometimes entity class names via $ref/descriptions. Put the endpoint behind the same firewall as the form.
  • Debugging: dump the resolver output first; a null or empty properties almost always means the form type wasn't recognized or a custom type lacks a transformer. Confirm the resolver actually received the bundle's autowired instance and not a fresh, unconfigured one.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core