# Doc: Doctrine ORM Best Practices (Doctrine 3.x / Symfony 7.x)

> Relevancia Wari: DT-001 (cascades), DT-002 (flush), DT-008 (N+1), DT-012 (fetch), DT-018 (doble flush), DT-020 (índices)

## Regla general: queries solo en repositorios

Nunca en servicios ni controladores. El controlador inyecta el repositorio o lo recibe via EntityValueResolver.

```php
// ✅ En repositorio
class GrupoRepository extends ServiceEntityRepository
{
    public function findActiveByUser(Usuario $user): array
    {
        return $this->createQueryBuilder('g')
            ->innerJoin('g.miembros', 'm')
            ->addSelect('m')                    // evita N+1
            ->where('m.usuario = :user')
            ->setParameter('user', $user)
            ->getQuery()
            ->getResult();
    }
}
```

## persist() vs flush()

- `persist()`: registra el objeto en el UnitOfWork. Sin query.
- `flush()`: ejecuta **todas** las operaciones pendientes en una sola transacción.
- **Una sola llamada a `flush()` al final de la operación completa**, no una por entidad.

```php
// ❌ MAL
$em->persist($a); $em->flush();
$em->persist($b); $em->flush();

// ✅ BIEN
$em->persist($a);
$em->persist($b);
$em->flush();
```

## Cascades — usar con criterio

`cascade: ['persist', 'remove']` en OneToMany puede borrar datos de forma no intencionada al eliminar el padre.

- `cascade: ['persist']`: útil para agregar hijos nuevos al persistir el padre.
- `cascade: ['remove']`: solo si la existencia del hijo no tiene sentido sin el padre (ej. `VotoFecha` sin `FechaPropuesta`). **Evitar en relaciones con entidades "compartidas"** (ej. `Usuario → Evento`).
- Para borrado seguro, preferir **soft delete** o desvinculación explícita en un servicio.

## Problema N+1 y cómo evitarlo

Ocurre cuando se itera una colección y Doctrine lanza una query por cada elemento.

```php
// ❌ MAL — N+1 si se accede a $grupo->getMiembros() después
$grupos = $repo->findAll();

// ✅ BIEN — join explícito con addSelect
$qb->innerJoin('g.miembros', 'm')->addSelect('m');
```

Regla: si vas a iterar una relación, inclúyela en el `addSelect` de la query que la carga.

## Fetch strategy

Doctrine 3.x usa LAZY por defecto en ManyToOne. Ser explícito cuando importa:

```php
#[ORM\ManyToOne(fetch: 'LAZY')]
private Grupo $grupo;

// EAGER solo si siempre necesitas el objeto relacionado
#[ORM\ManyToOne(fetch: 'EAGER')]
private TipoPublicacion $tipo;
```

No usar EAGER en OneToMany o ManyToMany (puede traer colecciones enteras sin control).

## Enums como tipo de columna

```php
#[ORM\Column(enumType: RolGrupo::class)]
private RolGrupo $rol;
```

Más seguro que guardar strings o JSON. Doctrine 3 lo soporta nativamente.

## Índices

Añadir índices explícitos en campos usados en WHERE, JOIN, ORDER BY o GROUP BY:

```php
#[ORM\Entity]
#[ORM\Index(name: 'idx_grupo_miembro', columns: ['grupo_id', 'usuario_id'])]
class GrupoMiembro { ... }
```

## Transacciones explícitas

Para operaciones que deben ser atómicas:

```php
$em->wrapInTransaction(function (EntityManagerInterface $em) {
    $em->persist($a);
    $em->persist($b);
    // flush automático al salir sin excepciones
});
```

## EntityValueResolver

Simplifica controladores: Symfony resuelve la entidad por el parámetro de ruta.

```php
#[Route('/grupos/{id}')]
public function show(Grupo $grupo): Response
{
    // $grupo ya cargado por Doctrine automáticamente
}
```

Para campos no-ID:

```php
#[Route('/grupos/{slug:grupo}')]
public function show(Grupo $grupo): Response { ... }
// Ejecuta findOneBy(['slug' => $slug])
```

## Errores comunes

| Error | Corrección |
|---|---|
| `flush()` dentro de un loop | Un solo `flush()` al final |
| Cascade remove en relaciones compartidas | Soft delete o desvinculación explícita |
| Query en servicio o controlador | Mover a repositorio |
| Iterar colección sin eager load | Añadir `addSelect()` al QueryBuilder |
| Sin índices en campos de filtro | Añadir `#[ORM\Index]` en la entidad |
