rogerdsh/liform-bundle
A Symfony bundle that generates JSON Schema from Symfony forms, exposing form structure for client-side rendering and validation. Integrates Liform with Symfony to produce schema output for APIs and JavaScript form builders.
First, be clear about what this package is: rogerdsh/liform-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/liform.php. It wraps the liform/liform library (the Limenius project) so that a Symfony FormInterface can be serialized into a JSON Schema document. In a Laravel codebase you will not get day-to-day value from it directly — Laravel has no Form component to transform. It becomes relevant in three concrete situations:
Minimal install (inside the Symfony side of your stack):
composer require rogerdsh/liform-bundle
Register it in config/bundles.php (Flex will usually do this):
return [
// ...
Lim\LiformBundle\LiformBundle::class => ['all' => true],
];
Then confirm the container actually has the service before writing any code:
bin/console debug:container liform
bin/console debug:container --tag=twig.extension | grep -i liform
First use case: take one existing form type and dump its schema from a controller.
// src/Controller/SchemaController.php
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Form\FormFactoryInterface;
public function schema(FormFactoryInterface $forms): JsonResponse
{
$form = $forms->create(RegistrationType::class);
return new JsonResponse($this->liform->transform($form));
}
If you are on the Laravel side, the equivalent first move is to not install the bundle: install liform/liform standalone, and build the Symfony\Component\Form\FormFactory via symfony/form + symfony/validator wired by hand. That is more code than it sounds, which is the honest reason most Laravel teams skip this and use opis/json-schema or a hand-maintained schema file instead.
The very first thing to check before investing: the fork's composer.json — its require block for symfony/form, php, and liform/liform. With 0 stars and 0 dependents this is an unmaintained fork; pin it to an exact version or a commit hash, and vendor-review the diff against upstream limenius/liform-bundle.
Pattern 1 — Schema endpoint as the single source of truth. Expose one route per form type, cache the result, and let the SPA fetch it.
#[Route('/api/schema/{form}', methods: ['GET'])]
public function show(string $form, FormFactoryInterface $forms, CacheInterface $cache): JsonResponse
{
$schema = $cache->get('schema.'.$form, function () use ($forms, $form) {
$map = [
'registration' => RegistrationType::class,
'checkout' => CheckoutType::class,
];
return $this->liform->transform($forms->create($map[$form]));
});
return new JsonResponse($schema);
}
Note the whitelist — never accept a class name from the request, since create() will instantiate anything.
Pattern 2 — Twig-side embedding. If the Symfony app renders pages, the bundle's Twig extension lets you embed the schema inline and boot the JS renderer from it:
<script>
window.__SCHEMA__ = {{ liform(form) }};
</script>
Verify the exact function/filter name with bin/console debug:twig | grep -i liform — this is the single most common source of "it does nothing" reports on these forks.
Pattern 3 — Constraint-driven schemas. The real leverage is that your existing Symfony Validator constraints become required, minLength, pattern, enum, etc. Put validation on the DTO/entity, not the form type, so both the HTML form and the JSON Schema inherit it:
class RegistrationInput
{
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\Length(min: 12, max: 128)]
public string $password;
}
Then data_class on the form type. This is the Symfony analogue of Laravel's FormRequest rules — and it is the piece a Laravel dev should port conceptually: one rules definition, two consumers (server validation, client schema).
Pattern 4 — Submitting against the schema. The bundle only produces schemas; submission still goes through normal Symfony form handling. Keep it that way. Do not try to feed the JSON Schema back into the server as validation — validate with the form/validator as usual, and treat the schema as a client hints document only.
Pattern 5 — Bridging into Laravel. If you must consume this from Laravel, treat the Symfony service as external and hit it over HTTP (Pattern 1) rather than trying to boot the bundle in-process. Cache the schema in Laravel with Cache::remember() keyed by form name plus a schema version, and validate the cached payload with json_decode(..., flags: JSON_THROW_ON_ERROR) plus a opis/json-schema self-check before serving.
Pattern 6 — Custom field mapping. When a custom form type renders as a useless {"type": "string"}, register a transformer rather than patching the bundle. That is the intended extension point; the bundle's config typically accepts a list of transformer services. Check bin/console config:dump-reference liform for the exact keys on this fork — the config tree is where forks diverge most.
Lim\LiformBundle) while changing the Packagist name. composer require rogerdsh/liform-bundle may succeed while RogerDS\LiformBundle\... does not exist. Always trust composer show -a rogerdsh/liform-bundle and the autoload.psr-4 block, not the repo name."repositories": [{ "type": "vcs", "url": "https://github.com/RogerDS/LiformBundle" }]
and pin "rogerdsh/liform-bundle": "dev-master#<sha>".packages/. Never let a transitive composer update move it.dependencies, const, or exclusiveMinimum-as-number. Normalize on the client or post-process the array before serving.required is derived from NotBlank/NotNull, not from PHP nullability. A nullable typed property with no constraint is optional; a non-nullable one is not automatically required in every version. Assert explicitly.ChoiceType maps to enum with the labels, not the values, in some configurations. Verify with a scratch controller before shipping — this mismatch breaks SPA submits and is a classic footgun.DateTimeType generally degrade to opaque strings. Handle them with custom transformers, or accept a hand-written schema fragment merged over the generated one.dump($this->liform->transform($form)); // look for empty "properties": []
An empty properties array almost always means data_class is missing on the form type.FormRequest-to-schema mapper (loop $request->rules(), switch on rule string) — a few hundred lines, fully owned, no Symfony dependency. Evaluate that before adopting this bundle into a Laravel project.How can I help you explore Laravel packages today?