destro/json-schema-form-bundle
Installation:
composer require cyve/json-schema-form-bundle
Add the bundle to config/bundles.php:
return [
// ...
Cyve\JsonSchemaFormBundle\JsonSchemaFormBundle::class => ['all' => true],
];
First Use Case:
Define a JSON schema (e.g., schemas/product.json):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Product",
"type": "object",
"properties": {
"name": { "type": "string" },
"price": { "type": "number" }
},
"required": ["name"]
}
Load and generate a form in a controller:
use Cyve\JsonSchemaFormBundle\Form\Type\SchemaType;
use Cyve\JsonSchemaFormBundle\Validator\Constraint\Schema;
public function createForm(Request $request)
{
$schema = json_decode(file_get_contents(__DIR__.'/schemas/product.json'));
$form = $this->createForm(SchemaType::class, new \StdClass(), [
'data_schema' => $schema,
'constraints' => [new Schema($schema)],
]);
return $this->renderForm($form);
}
Twig Integration:
Use the form_row or form_widget functions in your template:
{{ form_start(form) }}
{{ form_row(form) }}
<button type="submit">Save</button>
{{ form_end(form) }}
Schema-Driven Form Generation:
schemas/ directory) for consistency.$schema = json_decode(file_get_contents("schemas/{$role}_schema.json"));
Nested Forms:
Handle nested objects/arrays by leveraging SchemaType recursively:
{
"properties": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" }
}
}
}
}
The bundle automatically generates nested SchemaType forms.
Validation Integration:
Use the Schema constraint for validation:
$form->add('product', SchemaType::class, [
'data_schema' => $schema,
'constraints' => [new Schema($schema)],
]);
Errors are mapped to JSON schema keywords (e.g., required, type).
Dynamic Schema Loading: Fetch schemas from an API or database:
$schema = json_decode($apiClient->get('/schemas/product'));
Symfony Forms Extensions:
Extend the bundle’s SchemaType to add custom logic:
use Cyve\JsonSchemaFormBundle\Form\Type\SchemaType;
class CustomSchemaType extends SchemaType {
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'custom_option' => 'default_value',
]);
}
}
Register the extension in services.yaml:
services:
App\Form\Type\CustomSchemaType:
tags: [form.type]
Event Listeners:
Attach listeners to FormEvents for pre/post-processing:
$form->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
// Modify data before form rendering
});
CSRF and CSRF Protection: Ensure the form includes CSRF tokens:
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
Schema Validation:
justinrainbow/json-schema to validate schemas before form generation:
use Justinrainbow\JsonSchema\Validator;
$validator = new Validator();
$validation = $validator->validate($schema, (object) ['$ref' => 'http://json-schema.org/draft-07/schema#']);
if (count($validation->errors())) {
throw new \RuntimeException("Invalid JSON schema");
}
Circular References:
$ref circular references may cause infinite recursion. Use draft-07 or later and ensure references are resolvable:
{
"$ref": "#/definitions/user"
}
Form Options Overrides:
label, attr) in the schema are ignored. Override them explicitly:
$form->add('name', null, [
'label' => 'Custom Label',
'attr' => ['placeholder' => 'Enter name'],
]);
Type Mismatches:
type: "integer" → IntegerType (not NumberType).type: "string" with format: "date" → BirthdayType (if using Symfony UX).SchemaType.Performance:
$schemaCache = new \Symfony\Component\Cache\SimpleFileCache(__DIR__.'/cache');
$schema = $schemaCache->get('product_schema', function() {
return json_decode(file_get_contents('schemas/product.json'));
});
Schema Dumping: Dump the resolved schema to debug mappings:
$form = $this->createForm(SchemaType::class, $data, ['data_schema' => $schema]);
dump($form->getConfig()->getOption('data_schema')); // Inspect the schema
Form Type Hierarchy:
Use debug:form to inspect the generated form structure:
php bin/console debug:form your_form_name
Validation Errors: Check for validation errors in the Symfony profiler or via:
$form->isValid();
$form->getErrors(true); // Recursive errors
Custom Form Types:
Extend the bundle’s type mapping by overriding the getFormType() method in SchemaType:
protected function getFormType($type, array $options) {
if ($type === 'custom-type') {
return CustomType::class;
}
return parent::getFormType($type, $options);
}
Constraint Validation:
Add custom validation logic to the Schema constraint:
use Cyve\JsonSchemaFormBundle\Validator\Constraint\SchemaValidator;
class CustomSchemaValidator extends SchemaValidator {
public function validateValue($value, Constraint $constraint) {
if ($value === 'forbidden') {
$this->context->buildViolation('Value is forbidden')
->atPath('forbidden_field')
->addViolation();
}
parent::validateValue($value, $constraint);
}
}
Register the validator in services.yaml:
services:
App\Validator\Constraints\CustomSchemaValidator:
tags: [validator.constraint_validator]
Schema Resolvers: Implement a custom schema resolver for remote or dynamic schemas:
use Cyve\JsonSchemaFormBundle\Resolver\SchemaResolverInterface;
class ApiSchemaResolver implements SchemaResolverInterface {
public function resolve($schema) {
if (str_starts_with($schema, 'http')) {
return json_decode(file_get_contents($schema));
}
return $schema;
}
}
Bind it in services.yaml:
services:
App\Resolver\ApiSchemaResolver:
tags: [json_schema_form.schema_resolver]
How can I help you explore Laravel packages today?