Installation:
composer require limenius/liform-bundle
Ensure your project uses Symfony 5.4+, 6.x, 7.x, or 8.x.
Enable the Bundle:
Add to config/bundles.php (Symfony 5.4+) or AppKernel.php (legacy):
Limenius\LiformBundle\LimeniusLiformBundle::class => ['all' => true],
First Use Case: Transform a Symfony form into JSON Schema in a controller:
use Symfony\Component\Form\FormFactoryInterface;
class MyController extends AbstractController {
public function generateSchema(FormFactoryInterface $formFactory): JsonResponse {
$form = $formFactory->createNamedBuilder('my_form', FormType::class)
->add('field', TextType::class)
->getForm();
$schema = $this->get('liform')->transform($form);
return new JsonResponse($schema);
}
}
Frontend Integration:
Use the schema with libraries like liform-react or json-editor. Example API endpoint:
# config/routes.yaml
generate_schema:
path: /api/schema
controller: App\Controller\MyController::generateSchema
methods: GET
Form Definition:
Define your Symfony form types with metadata (e.g., title, description, attr for UI hints). Example:
$builder->add('name', TextType::class, [
'label' => 'Full Name',
'attr' => ['placeholder' => 'Enter your name'],
'description' => 'Required field',
]);
Schema Generation:
Inject Limenius\LiformBundle\Liform service and call transform():
$schema = $this->get('liform')->transform($form);
action (URL) and method (HTTP verb) for form submission.CollectionType) and embedded forms.Frontend Synchronization:
$initialData = $this->get('serializer')->normalize($form->createView());
$errors = $this->get('serializer')->normalize($form);
API-Driven Forms:
Expose schemas via API endpoints (e.g., /api/forms/{type}/schema) and use them to:
Symfony UX Integration: Combine with Symfony UX (e.g., Turbo/Stimulus) for reactive forms:
// Frontend (liform-react)
<JsonEditor
schema={schemaFromApi}
onChange={handleFormChange}
error={formErrors}
/>
Dynamic Schema Updates: Regenerate schemas on-the-fly for dynamic forms (e.g., conditional fields):
// Controller
$form = $this->createForm(DynamicFormType::class, $data, [
'conditional_fields' => $request->query->get('fields'),
]);
$schema = $this->get('liform')->transform($form);
Caching: Cache generated schemas (e.g., with Symfony Cache component) to avoid reprocessing:
$cacheKey = 'form_schema_' . md5($formTypeClass);
$schema = $this->get('liform.cache')->get($cacheKey, function() use ($form) {
return $this->get('liform')->transform($form);
});
Testing: Test schemas against expected JSON structures:
$schema = $this->get('liform')->transform($form);
$this->assertJsonStringEqualsJsonString(
file_get_contents(__DIR__.'/expected_schema.json'),
json_encode($schema)
);
CSRF Protection: Disable CSRF for API forms to avoid schema bloat:
$form = $this->createForm(FormType::class, $data, ['csrf_protection' => false]);
Circular References: Avoid circular references in form types (e.g., self-referential collections) as they may cause infinite loops during schema generation.
Symfony 6+ Changes:
FormBuilderInterface (not FormFactoryInterface) for newer Symfony versions.Schema Validation:
Liform generates schemas but doesn’t validate them. Use tools like ajv on the frontend to enforce schema rules.
Transformer Issues: If a form field isn’t rendered correctly, check:
block_prefix of the form type (e.g., Symfony\Component\Form\Extension\Core\Type\TextType → text).Extensions Not Applying:
Ensure extensions are tagged correctly in services.yaml:
services:
app.liform.my_extension:
class: App\Liform\MyExtension
tags:
- { name: liform.extension, priority: 100 } # Priority affects order
Initial Data Mismatch: Verify that form data matches the schema structure. Use:
$view = $form->createView();
$data = $this->get('serializer')->normalize($view);
dump($data); // Debug shape
Custom Transformers:
Extend default behavior for specific form types (e.g., ChoiceType):
// src/Liform/CustomChoiceTransformer.php
class CustomChoiceTransformer extends AbstractTransformer {
public function transformField(FieldInterface $field, FormInterface $form): array {
$schema = parent::transformField($field, $form);
$schema['choices'] = $form->getConfig()->getOption('choices');
return $schema;
}
}
Register in services.yaml:
services:
app.liform.choice_transformer:
class: App\Liform\CustomChoiceTransformer
tags:
- { name: liform.transformer, form_type: choice }
Global Extensions: Modify all schemas (e.g., add metadata):
class GlobalExtension implements ExtensionInterface {
public function apply(FormInterface $form, array $schema): array {
$schema['metadata'] = [
'version' => '1.0',
'author' => 'Your Name',
];
return $schema;
}
}
Conditional Logic: Use extensions to add dynamic properties:
class ConditionalExtension implements ExtensionInterface {
public function apply(FormInterface $form, array $schema): array {
if ($form->has('is_active') && $form->get('is_active')->getConfig()->getOption('required')) {
$schema['required'][] = 'is_active';
}
return $schema;
}
}
Symfony 8+:
Use config/packages/liform.yaml for bundle config (if available). Example:
limenius_liform:
default_options:
ignore_unknown_fields: true # Skip fields without transformers
Performance: Disable debug mode for production to avoid schema regeneration on every request:
$this->container->get('liform')->setDebug(false);
Frontend Compatibility:
Ensure your frontend library supports the generated schema. For liform-react, use:
<JsonEditor
schema={schema}
theme="bootstrap3"
show_errors={true}
/>
How can I help you explore Laravel packages today?