# Doc: Symfony Forms Best Practices (Symfony 7.x)

> Relevancia Wari: formularios en toda la app (GrupoFormType, PerfilFormType, etc.)

## Regla principal: un FormType dedicado por entidad/caso de uso

Nunca construir formularios a mano en el controlador.

```php
class GrupoFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('nombre', TextType::class)
            ->add('descripcion', TextareaType::class)
            ->add('imagen', FileType::class, ['required' => false, 'mapped' => false]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Grupo::class,
        ]);
    }
}
```

`data_class` siempre explícito — es lo que conecta el formulario con la entidad.

## Validaciones: en la entidad, no en el formulario

Ver `doc-symfony-validator.md`. El formulario puede añadir constraints solo para validaciones específicas de la UI (ej. confirmación de contraseña, campo no mapeado).

```php
// Solo cuando el campo no existe en la entidad
->add('confirmPassword', PasswordType::class, [
    'mapped' => false,
    'constraints' => [new Assert\NotBlank()],
])
```

## Patrón en controlador

```php
public function new(Request $request): Response
{
    $grupo = new Grupo();
    $form = $this->createForm(GrupoFormType::class, $grupo);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // $grupo ya tiene los datos del formulario aplicados
        $this->entityManager->persist($grupo);
        $this->entityManager->flush();

        return $this->redirectToRoute('grupo_show', ['id' => $grupo->getId()]);
    }

    return $this->render('grupo/new.html.twig', ['form' => $form]);
}
```

- Crear el objeto **antes** del formulario.
- Usar `handleRequest()`, no `submit()`.
- Comprobar `isSubmitted() && isValid()` juntos.
- Redirigir tras éxito (patrón POST-Redirect-GET).

## Campos no mapeados

Para datos que no pertenecen a la entidad (ficheros que se procesan aparte, confirmaciones):

```php
->add('imagenFile', FileType::class, ['mapped' => false, 'required' => false])
```

```php
$imagenFile = $form->get('imagenFile')->getData();
if ($imagenFile) {
    $this->imageService->save($imagenFile, $grupo);
}
```

## Inyección de servicios en FormType

Con autowiring funciona automáticamente:

```php
class EventoFormType extends AbstractType
{
    public function __construct(
        private JuegoRepository $juegoRepository
    ) {}
}
```

## Errores comunes

| Error | Corrección |
|---|---|
| Datos seteados después de crear el form | Poblar el objeto antes de `createForm()` |
| `getData()` devuelve null | Comprobar `isSubmitted() && isValid()` antes |
| Constraints solo en el formulario | Moverlas a la entidad |
| Lógica de negocio en `buildForm()` | Moverla al servicio |
| Sin `data_class` en `configureOptions()` | Siempre definirlo |
