kunicmarko/form-annotation-bundle
A Symfony bundle for defining forms with annotations on your entity classes. Configure fields and form options directly in code, reduce FormType boilerplate, and build forms automatically. Useful for rapid prototyping and smaller applications.
Prerequisites check first. This bundle was last released in February 2018, targets the Symfony 3.3/4.0 era, and is archived. Before writing any code, confirm your project runs PHP 7.x with doctrine/annotations and a Symfony version old enough for the bundle's service definitions. On Symfony 5+ or PHP 8 you will hit deprecation and autowiring problems almost immediately.
Install and register.
composer require kunicmarko/form-annotation-bundle
Register the bundle manually (pre-Flex style) in app/AppKernel.php:
$bundles = [
// ...
new Kunicmarko\FormAnnotationBundle\FormAnnotationBundle(),
];
On a Flex app, add it to config/bundles.php.
Where to look first. The repository README is the only documentation; read it end to end before touching code, then inspect the Annotation/ directory (the annotation classes and their accepted options) and the service that reads them (typically a form factory/builder service). The tests are the most reliable specification of behaviour, since the README from 2018 may not match edge cases.
First use case. The intended workflow is to describe form fields declaratively on a class (usually an entity or form model) instead of hand-writing a FormType, then let the bundle build the form. Start with a single small entity and one controller action, render the form, submit it, and confirm the persisted result. Do not retrofit a large domain model on the first attempt — the annotation syntax and option merging are the parts most likely to surprise you.
Annotate the model, build in the controller. The core pattern is field-level annotations that declare a form type and options, consumed by a builder service:
use Kunicmarko\FormAnnotationBundle\Annotation as Form;
class Post
{
/** @Form\Field(type="text", options={"label": "Title"}) */
private $title;
/** @Form\Field(type="textarea", options={"attr": {"rows": 5}}) */
private $body;
}
The exact annotation names and namespace alias come from the bundle's Annotation/ folder — verify them against the README before copying examples, as this package's docs are sparse.
Wire it into a CRUD action. Inject the bundle's form-building service into your controller (or fetch it from the container in a legacy app), pass the annotated object, and handle the request normally:
public function edit(Post $post, Request $request, FormBuilder $builder)
{
$form = $builder->createForm($post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->em->flush();
return $this->redirectToRoute('post_edit', ['id' => $post->id]);
}
return $this->render('post/edit.html.twig', ['form' => $form->createView()]);
}
Use DTOs for anything non-trivial. Because the annotations live on the class, they leak into your persistence layer. A common day-to-day pattern is a dedicated form model (DTO) annotated for the form, mapped to the entity in the controller. This keeps Doctrine metadata clean and makes it easy to have separate "create" and "edit" models with different field sets.
Mix with conventional form types. Treat the annotation-built form as a starting point: add fields imperatively with $form->add(...) after creation, or embed the generated form inside a larger FormType via a custom type. This is the pragmatic path when only part of a form fits the annotation model.
Reuse across contexts. Annotated classes are convenient for admin panels, prototypes, and internal tools where the same entity is rendered with slight variations. If two screens need materially different forms, prefer two DTOs over trying to conditionally mutate options at runtime.
doctrine/annotations is deprecated and many teams have moved to native attributes. This bundle predates that entirely, so migration means rewriting the metadata layer — usually as explicit FormType classes, which is the better long-term answer anyway.bin/console cache:clear) before concluding that a change "didn't work".$form->getIterator() / debug:form) and verify the annotation is actually being read.options interact. When attr, required, or label behave unexpectedly, add the field imperatively to confirm the annotation layer is the cause.bin/console debug:container to find the builder service, debug:form to inspect resolved types, and the bundle's own tests as behavioural documentation. If the service will not autowire, define it explicitly in services.yaml — 2018-era service definitions rarely autowire cleanly on modern Symfony.FormType configuration, with no annotation cache, no PHP 8 friction, and no abandoned dependency. Use this bundle only to understand or maintain an existing legacy integration.How can I help you explore Laravel packages today?