Install Ibexa DXP (prerequisite): Follow the official Ibexa DXP installation guide to set up the core system. Ensure you’re using PHP 8.3+ and Symfony 7.4+ (as of v5.0.x).
Enable the Package:
Add ibexa/content-forms to your composer.json:
composer require ibexa/content-forms
Register the bundle in config/bundles.php:
return [
// ...
Ibexa\ContentForms\IbexaContentFormsBundle::class => ['all' => true],
];
First Use Case: Basic Content Form
Create a form type for a content type (e.g., Article):
use Ibexa\ContentForms\FieldType\FieldTypeFormFactory;
use Symfony\Component\Form\AbstractType;
class ArticleFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('body', TextareaType::class)
->add('save', SubmitType::class);
}
}
Register the form in your content type definition (YAML/JSON) under form:
form:
name: article_form
class: App\Form\ArticleFormType
Access the Form in a Controller:
Use the ContentFormFactory to generate and handle the form:
use Ibexa\ContentForms\ContentFormFactory;
class ArticleController
{
public function edit(ContentFormFactory $formFactory, Content $content)
{
$form = $formFactory->create($content);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$form->save();
return $this->redirectToRoute('article_show', ['id' => $content->id]);
}
return $this->render('article/edit.html.twig', ['form' => $form->createView()]);
}
}
Content Forms as a Bridge: The package bridges Symfony Forms with Ibexa’s Content and User objects. Use it to:
ezimage, ezrichtext).Field Type Transformers:
Ibexa provides built-in transformers for common field types (e.g., ezstring, ezdate). Extend FieldTypeTransformerInterface for custom fields:
use Ibexa\ContentForms\FieldType\FieldTypeTransformerInterface;
class CustomFieldTransformer implements FieldTypeTransformerInterface
{
public function transformToFormData($value): ?string
{
return json_encode($value);
}
public function transformFromFormData($data): array
{
return json_decode($data, true);
}
}
Register it in services.yaml:
services:
Ibexa\ContentForms\FieldType\FieldTypeTransformerInterface:
class: App\FieldType\CustomFieldTransformer
tags: { name: ibexa.content_forms.field_type_transformer, fieldType: 'custom' }
$content = $contentService->newContentCreateStruct('article');
$form = $formFactory->create($content);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$content = $form->getContent();
$contentService->publishVersion($content->versionInfo);
}
config/packages/ibexa_content_forms.yaml):
ibexa_content_forms:
autosave:
enabled: true
interval: 300 # seconds
UserFormType for custom user fields:
class UserProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('avatar', FileType::class, ['label' => 'Profile Image'])
->add('bio', TextareaType::class);
}
}
UserFormFactory in a controller:
$userForm = $userFormFactory->create($user);
$userForm->handleRequest($request);
if ($userForm->isSubmitted() && $userForm->isValid()) {
$userForm->save();
}
use Ibexa\ContentForms\Event\FormActionEvent;
$eventDispatcher->dispatch(new FormActionEvent($form, 'post_save', function (FormActionEvent $event) {
// Send email logic
}));
use Ibexa\ContentForms\Event\FormSubmitEvent;
$eventDispatcher->addListener(FormSubmitEvent::class, function (FormSubmitEvent $event) {
if (!$event->getForm()->isValid()) {
$event->stopPropagation();
}
});
$builder->add('title', TextType::class, [
'constraints' => [
new NotBlank(),
new Length(['min' => 10]),
],
]);
fieldDefinitions:
title:
fieldType: ezstring
constraints:
- Ibexa\Contracts\Core\FieldType\Constraint\NotEmpty
Field Type Mismatches:
RuntimeException.FieldTypeTransformerInterface and tag the service correctly.ibexa.content_forms.field_type_transformer tags in services.yaml.Autosave Conflicts:
$eventDispatcher->addListener(FormActionEvent::class, function (FormActionEvent $event) {
if ($event->getForm()->getContent()->contentInfo->contentId === 123) {
$event->setAutosave(false);
}
});
Draft Ownership:
system_user instead of the logged-in user).FormActionEvent to set the owner:
$event->setDraftOwner($this->getUser());
Non-Translatable Fields:
$builder->add('non_translatable_field', null, [
'mapped' => false,
'data' => $content->getFieldValue('non_translatable_field'),
]);
File Uploads:
ezimage field type constraints in YAML:
fieldDefinitions:
image:
fieldType: ezimage
constraints:
- Ibexa\Contracts\Core\FieldType\Constraint\MaxFileSize:
maxSize: 5M
Enable Form Debugging:
Add this to config/packages/dev/ibexa_content_forms.yaml:
ibexa_content_forms:
debug: true
This logs form data and events to var/log/dev.log.
Check Event Dispatching:
Use Symfony’s event profiler (_profiler) to verify events are fired:
$eventDispatcher->addListener(FormSubmitEvent::class, function (FormSubmitEvent $event) {
$this->logger->debug('Form submitted', ['content_id' => $event->getContent()->id]);
});
Validate Field Transformers: Test transformers in isolation:
$transformer = $container->get
How can I help you explore Laravel packages today?