Symfony cumplió veinte años en 2025 y sigue siendo el framework de referencia cuando el proyecto es grande, tiene que durar mucho tiempo o necesita estabilidad contractual. La versión 7.2, publicada en noviembre de 2025, es LTS con soporte hasta 2028. Si arrancas un proyecto hoy, es la versión que deberías usar.
Symfony o Laravel: cuándo elegir cada uno
Laravel ha ganado popularidad enorme en los últimos años y con razón: las convenciones sobre configuración funcionan bien, la curva de aprendizaje es menor y para startups o proyectos de tamaño medio te ahorra tiempo. Symfony va en otra dirección. Todo es explícito, cada pieza se configura, y eso que al principio parece engorroso acaba siendo una ventaja cuando el equipo crece o el proyecto lleva tres años en producción.
Hay otro detalle que mucha gente no sabe: Laravel usa varios componentes de Symfony internamente. Drupal, Magento y phpBB también. Symfony no es solo un framework; es una colección de componentes PHP independientes que otros proyectos reutilizan. Cuando eliges Symfony completo, estás eligiendo esa base más una capa de configuración y convenciones propias.
Para proyectos enterprise con equipos grandes, contratos de mantenimiento largos o requisitos de personalización muy específicos, Symfony gana. Para proyectos medianos donde quieres ir rápido y el equipo ya conoce Laravel, Laravel gana. No hay una respuesta universal.
Los componentes: el valor real de Symfony
Puedes instalar componentes de Symfony sin el framework completo. Eso es lo que hacen muchos proyectos PHP que no usan Symfony como tal pero sí aprovechan partes concretas.
symfony/http-foundation: abstracción de peticiones y respuestas HTTPsymfony/routing: sistema de rutassymfony/console: comandos de terminalsymfony/event-dispatcher: sistema de eventossymfony/serializer: serialización y deserialización, con soporte de enums de PHP desde Symfony 7
Si en tu proyecto necesitas solo el serializador o solo el despachador de eventos, puedes instalarlo con Composer sin cargar nada más. Eso tiene bastante valor para proyectos que no quieren un framework completo pero sí componentes probados.
El DI container: la pieza central
El contenedor de inyección de dependencias de Symfony es uno de los mejores que hay en PHP. Funciona con autowiring: le dices qué clase necesitas en el constructor y el container la resuelve solo por el tipo declarado, sin que tengas que configurar nada manualmente.
En Symfony 7 puedes registrar servicios directamente con atributos PHP, sin tocar ningún YAML ni XML:
#[AsController]
class GetUserController
{
public function __construct(private UserRepository $repo) {}
#[Route('/usuarios/{id}', methods: ['GET'])]
public function __invoke(int $id): Response
{
return new JsonResponse($this->repo->find($id));
}
}
El atributo #[AsController] registra la clase como servicio. El atributo #[Route] declara la ruta. El container resuelve UserRepository automáticamente. No hay que declarar nada más en ningún fichero de configuración.
Para listeners tienes #[AsEventListener] y para comandos de consola #[AsCommand]. El patrón es el mismo en los tres casos.
Inyectar parámetros de configuración con #[Autowire]
Symfony 7 mejora el atributo #[Autowire] para inyectar parámetros de configuración directamente en el constructor sin necesidad de declararlos como servicios:
class ApiClient
{
public function __construct(
#[Autowire('%app.api_key%')] private string $apiKey,
#[Autowire('%app.timeout%')] private int $timeout,
) {}
}
Antes había que pasar por el services.yaml para hacer esto. Ahora el atributo lo resuelve solo.
Rutas y controladores
Las rutas con atributos llevan tiempo disponibles pero en Symfony 7 son la forma recomendada y la que verás en toda la documentación oficial:
#[Route('/usuarios/{id}', name: 'user_show', methods: ['GET'])]
public function show(int $id): Response
{
// ...
}
El patrón {id<d+>} añade un requirement de regex directamente en el atributo. Antes ese requirement iba en un array separado; ahora va inline dentro del nombre del parámetro con < y >.
Los controladores invokables son una buena opción para rutas con lógica simple: una clase, un método __invoke, una responsabilidad. Escalan bien cuando el número de endpoints crece porque cada controlador vive en su propio fichero.
Doctrine como ORM
Symfony no incluye ORM en el core, pero Doctrine es el estándar de facto en proyectos Symfony y la integración es muy buena. La definición de entidades con atributos PHP queda bastante limpia:
#[ORMEntity]
#[ORMTable(name: 'usuarios')]
class Usuario
{
#[ORMId]
#[ORMGeneratedValue]
#[ORMColumn]
private int $id;
#[ORMColumn(length: 180, unique: true)]
private string $email;
}
Para gestionar el esquema de la base de datos usas migraciones. El flujo habitual es generar la migración automáticamente comparando la entidad con el esquema actual, revisarla y ejecutarla:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Para queries complejas tienes el QueryBuilder y DQL (Doctrine Query Language), que funciona parecido a SQL pero sobre entidades en vez de tablas. Para consultas muy específicas siempre puedes caer a SQL nativo.
Symfony Messenger: mensajes y comandos
El componente symfony/messenger implementa un bus de mensajes que encaja bien con patrones CQRS. La idea es sencilla: defines un mensaje, defines un handler y despachas el mensaje desde donde lo necesites:
// El mensaje
class EnviarEmailBienvenida
{
public function __construct(public readonly int $userId) {}
}
// El handler
#[AsMessageHandler]
class EnviarEmailBienvenidaHandler
{
public function __invoke(EnviarEmailBienvenida $msg): void
{
// lógica de envío
}
}
// Despachar
$bus->dispatch(new EnviarEmailBienvenida($userId));
Lo que hace útil a Messenger en proyectos reales es el sistema de transportes. El mismo código funciona de forma síncrona en local y de forma asíncrona en producción usando Doctrine, Redis, RabbitMQ o Amazon SQS. Cambias el transporte en la configuración sin tocar el código de la aplicación.
AssetMapper: assets sin webpack obligatorio
Symfony AssetMapper llegó en la versión 6.3 y en Symfony 7 está completamente estabilizado. La idea es gestionar JavaScript moderno con importmaps sin necesitar un bundler ni node_modules.
php bin/console importmap:require @hotwired/stimulus
Ese comando descarga la librería directamente como asset en tu proyecto. El navegador la carga usando importmaps nativos. Para proyectos donde no necesitas un build step complejo, AssetMapper más Stimulus o Alpine.js cubre la mayoría de casos sin añadir complejidad de tooling.
Si tu proyecto tiene mucho frontend con procesado de CSS, TypeScript o módulos complejos, Webpack Encore sigue siendo la opción con más control. No es que uno sustituya al otro; son herramientas para necesidades distintas.
Testing en Symfony
Symfony tiene buena integración con PHPUnit y varias clases de utilidad para test que ahorran bastante trabajo. Para tests funcionales HTTP tienes WebTestCase:
class UserControllerTest extends WebTestCase
{
public function testGetUser(): void
{
$client = static::createClient();
$client->request('GET', '/api/usuarios/1');
$this->assertResponseIsSuccessful();
$this->assertJson($client->getResponse()->getContent());
}
}
No levanta un servidor real; simula las peticiones internamente. Para tests de integración donde necesitas acceso al container tienes KernelTestCase, que arranca el kernel de Symfony y te da acceso a cualquier servicio registrado.
Para mocks usas directamente createMock() de PHPUnit. El container de Symfony en tests soporta sobrescribir servicios con mocks, lo que facilita aislar dependencias externas en tests de integración.
PHP 8.2, 8.3 y 8.4
Symfony 7 exige PHP 8.2 como mínimo. PHP 8.3 y 8.4 están completamente soportados. Si usas Symfony 7.2 LTS con PHP 8.4, tienes acceso a las últimas características del lenguaje: property hooks, tipos asimétricos, clases abstractas anónimas y varias mejoras de rendimiento del motor.
El ciclo de soporte de Symfony LTS (3 años desde la publicación) encaja bien con proyectos que no pueden actualizar frecuentemente. Symfony 7.2 tiene soporte garantizado hasta noviembre de 2028, lo que te da margen para planificar actualizaciones sin urgencia.
Si quieres profundizar en cómo estructurar la comunicación entre componentes, puedes leer sobre arquitectura de eventos en PHP: de Laravel a Symfony. Y si estás pensando en exponer una API con Symfony, esta guía sobre construir APIs PHP con Symfony: estructura y contratos cubre los patrones habituales.
Imagen: Pexels / Markus Winkler
