Módulo 01: Instalación y estructura

Objetivos del módulo

  • Preparar el entorno: PHP 8.4+, extensiones necesarias y Composer
  • Instalar Symfony CLI en Windows, macOS y Linux y comprobar los requisitos
  • Crear el proyecto del blog con symfony new --webapp y con Composer
  • Entender la diferencia entre el esqueleto mínimo (skeleton) y la aplicación web (webapp)
  • Arrancar el servidor local, con HTTPS incluido
  • Conocer la estructura de directorios de un proyecto Symfony
  • Comprender Symfony Flex, las recipes y el fichero symfony.lock
  • Manejar los entornos dev, test y prod y los ficheros .env
  • Usar la consola bin/console, la Web Debug Toolbar y el Profiler
  • Generar código con Maker Bundle
  • Levantar una base de datos con Docker o usar MySQL/MariaDB local
  • Configurar VS Code o PhpStorm y crear tu primer “Hola mundo”

1. Requisitos

Symfony 8.1 necesita:

Requisito Versión / detalle
PHP 8.4 o superior (recomendado PHP 8.5)
Extensiones PHP obligatorias ctype, iconv, pcre, session, simplexml, tokenizer (casi siempre vienen activadas)
Extensiones recomendadas intl, mbstring, pdo_mysql / pdo_pgsql / pdo_sqlite, xml, curl, zip, openssl, fileinfo
Composer 2.x
Symfony CLI Opcional pero muy recomendable
Git Recomendable (Symfony CLI inicializa un repositorio en cada proyecto nuevo)
Docker Opcional, para levantar la base de datos

Comprueba lo que ya tienes instalado:

php -v
# PHP 8.5.x (cli) ...

php -m
# Lista de extensiones activas: ctype, curl, iconv, intl, mbstring, pdo_mysql...

composer --version
# Composer version 2.x.x

⚠️ Importante: Si php -v muestra una versión inferior a 8.4, Composer se negará a instalar Symfony 8 con un error del tipo “symfony/framework-bundle v8.1.x requires php >=8.4”. Actualiza PHP antes de seguir. Si necesitas trabajar con PHP 8.2 o 8.3, usa Symfony 7.4 LTS.

Instalar PHP y Composer

Windows (con Scoop, un gestor de paquetes para la terminal):

# Instalar Scoop (solo la primera vez)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

# Instalar PHP y Composer
scoop install php composer git

💡 Consejo: En Windows, las extensiones se activan en el php.ini. Ejecuta php --ini para ver qué fichero se está usando y descomenta (quita el ;) líneas como extension=intl, extension=pdo_mysql, extension=pdo_sqlite, extension=mbstring, extension=openssl o extension=zip.

macOS (con Homebrew):

brew install php composer

Linux (Debian/Ubuntu). Si tu distribución no trae PHP 8.4+, puedes usar el repositorio de Ondřej Surý en Ubuntu:

sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.5-cli php8.5-xml php8.5-intl php8.5-mbstring \
                 php8.5-curl php8.5-zip php8.5-mysql php8.5-sqlite3 unzip

# Composer: sigue las instrucciones de https://getcomposer.org/download/

2. Symfony CLI

📘 Concepto: Symfony CLI es un programa (el ejecutable symfony) que facilita el desarrollo: crea proyectos, comprueba requisitos, levanta un servidor web local con HTTPS, detecta tus contenedores Docker y ejecuta la consola y Composer con la versión correcta de PHP. No forma parte de tu proyecto: se instala una vez en tu ordenador.

Instalación

Windows (Scoop):

scoop install symfony-cli

macOS (Homebrew):

brew install symfony-cli/tap/symfony-cli

Linux Debian/Ubuntu (APT):

curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.deb.sh' | sudo -E bash
sudo apt install symfony-cli

Linux Fedora/RHEL (DNF):

curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.rpm.sh' | sudo -E bash
sudo dnf install symfony-cli

Cualquier sistema (instalador genérico):

curl -sS https://get.symfony.com/cli/installer | bash

Comprueba la instalación:

symfony version
# Symfony CLI version 5.x.x

Comprobar los requisitos

symfony check:requirements

Salida aproximada:

Symfony Requirements Checker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> PHP is using the following php.ini file:
C:\Users\TuUsuario\scoop\apps\php\current\cli\php.ini

> Checking Symfony requirements:

.............................W.......

 [OK]
 Your system is ready to run Symfony projects

Optional recommendations to improve your setup
----------------------------------------------

 * intl extension should be available
   > Install and enable the intl extension (used for validators).

💡 Consejo: Atiende también a las recomendaciones opcionales (marcadas con W). La extensión intl, por ejemplo, es necesaria para formatear fechas y números según el idioma, algo que usaremos en el blog.


3. Crear el proyecto del blog

Con Symfony CLI (recomendado)

symfony new blog --webapp

Esto descarga la última versión estable (8.1), instala todos los paquetes típicos de una aplicación web y crea un repositorio Git con un primer commit. Si quieres fijar la versión:

# Versión concreta
symfony new blog --version="8.1.*" --webapp

# Última LTS (7.4)
symfony new blog --version=lts --webapp

Con Composer (equivalente)

composer create-project symfony/skeleton:"8.1.*" blog
cd blog
composer require webapp

📘 Concepto: symfony new no es más que un atajo: por debajo ejecuta composer create-project symfony/skeleton y, con --webapp, añade el paquete symfony/webapp-pack. El resultado es idéntico.

Skeleton frente a webapp

  symfony/skeleton (sin --webapp) --webapp (skeleton + webapp-pack)
Pensado para Microservicios, APIs, aplicaciones de consola Aplicaciones web tradicionales con HTML
Paquetes Lo mínimo: FrameworkBundle, Console, Dotenv, Flex, Runtime, YAML Además: Twig, Doctrine ORM, formularios, validación, seguridad, Mailer, Messenger, Serializer, AssetMapper, Stimulus, Turbo, traducciones…
Herramientas de desarrollo Ninguna Maker Bundle, Web Profiler, Debug Bundle, PHPUnit
Tamaño de vendor/ Pequeño Mayor
Directorios bin, config, public, src, var, vendor Además: assets, migrations, templates, tests, translations

💡 Consejo: Para el blog del curso usaremos --webapp. Cuando hagas una API pura (módulo 12), quizá prefieras el skeleton e ir añadiendo solo lo necesario. Nada te impide además empezar con el skeleton y hacer composer require twig más tarde: así es como funciona Flex.


4. El servidor web local

Entra en la carpeta del proyecto y arranca el servidor:

cd blog
symfony server:start
 [OK] Web server listening
      The Web server is using PHP CLI 8.5.x
      http://127.0.0.1:8000

Abre esa URL en el navegador y verás la página de bienvenida de Symfony.

Comandos útiles del servidor

Comando Qué hace
symfony server:start Arranca el servidor y muestra los logs en la terminal (Ctrl+C para parar)
symfony server:start -d Lo arranca en segundo plano (daemon); también symfony serve -d
symfony server:start --open Arranca y abre el navegador
symfony open:local Abre el proyecto en el navegador
symfony server:log Muestra los logs del servidor en segundo plano
symfony server:status Indica si el servidor está en marcha
symfony server:list Lista todos los servidores locales activos
symfony server:stop Detiene el servidor en segundo plano

HTTPS en local

Muchas funcionalidades modernas del navegador (cookies seguras, service workers, HTTP/2) exigen HTTPS. Symfony CLI crea una autoridad de certificación local para que tengas HTTPS sin avisos:

symfony server:ca:install

Solo tienes que hacerlo una vez por ordenador. A partir de entonces, el servidor estará disponible en https://127.0.0.1:8000.

⚠️ Importante: El servidor de Symfony CLI es solo para desarrollo. En producción usarás Nginx/Apache con PHP-FPM, FrankenPHP o un contenedor Docker (lo verás en el módulo 15).

¿Y si no uso Symfony CLI?

Puedes usar el servidor integrado de PHP apuntando a public/:

php -S 127.0.0.1:8000 -t public

Funciona, pero pierdes el HTTPS, la integración con Docker y la detección automática de la versión de PHP.


5. Estructura de directorios

Tras crear el proyecto con --webapp, verás algo así:

blog/
├── assets/            ← JavaScript y CSS (AssetMapper)
├── bin/
│   └── console        ← la consola de Symfony
├── config/
│   ├── packages/      ← configuración de cada bundle (YAML)
│   ├── routes/        ← importación de rutas de bundles
│   ├── bundles.php
│   ├── routes.yaml
│   └── services.yaml
├── migrations/        ← migraciones de base de datos
├── public/
│   └── index.php      ← front controller
├── src/
│   ├── Controller/
│   ├── Entity/
│   ├── Repository/
│   └── Kernel.php
├── templates/
│   └── base.html.twig
├── tests/
├── translations/
├── var/               ← caché y logs (no se versiona)
├── vendor/            ← dependencias de Composer (no se versiona)
├── .env
├── .env.dev
├── .env.test
├── compose.yaml
├── compose.override.yaml
├── composer.json
├── composer.lock
├── importmap.php
├── phpunit.dist.xml
└── symfony.lock
Directorio / fichero Contenido ¿Lo tocas?
assets/ JavaScript (app.js), CSS (styles/app.css) y controladores Stimulus Sí, a menudo
bin/ Ejecutables, sobre todo bin/console Casi nunca
config/ Configuración: packages/ (un YAML por bundle), routes.yaml, services.yaml, bundles.php Sí
migrations/ Clases PHP generadas por Doctrine para cambiar el esquema de la BD Las generas y revisas
public/ Raíz web: lo único accesible desde el navegador (index.php, imágenes públicas, assets compilados) Poco
src/ Tu código PHP (namespace App\): controladores, entidades, servicios… Constantemente
templates/ Plantillas Twig Constantemente
tests/ Tests de PHPUnit Sí
translations/ Ficheros de traducción (YAML, XLIFF…) Si tu app es multidioma
var/ Caché (var/cache/), logs (var/log/) y otros datos generados No (se puede borrar)
vendor/ Dependencias instaladas por Composer Nunca
.env Variables de entorno por defecto (sí se versiona) Sí, con cuidado
compose.yaml Servicios Docker (base de datos, servidor de correo de pruebas…) A veces
importmap.php Paquetes JavaScript del frontend (AssetMapper) Mediante comandos
symfony.lock Registro de las recipes aplicadas por Flex (sí se versiona) Nunca a mano

⚠️ Importante: El servidor web debe apuntar siempre a public/, nunca a la raíz del proyecto. Si apuntases a la raíz, cualquiera podría descargar tu .env con las contraseñas.

💡 Consejo: En Symfony 8.1 la recipe del framework también crea un fichero AGENTS.md (y un CLAUDE.md que lo referencia) con indicaciones para asistentes de IA sobre las convenciones del proyecto. Puedes editarlo o eliminarlo si no lo usas.

Una mirada a config/services.yaml

# config/services.yaml
parameters:

services:
    _defaults:
        autowire: true      # Inyecta dependencias automáticamente
        autoconfigure: true # Registra automáticamente comandos, subscribers, extensiones Twig...

    # Cada clase de src/ se convierte en un servicio
    App\:
        resource: '../src/'

Gracias a estas pocas líneas, cualquier clase que crees en src/ puede inyectarse en controladores y otros servicios sin configurar nada. Lo estudiarás a fondo en el módulo 09.

Y config/routes.yaml

# config/routes.yaml
controllers:
    resource: routing.controllers

Esta entrada le dice a Symfony que cargue las rutas definidas con el atributo #[Route] en tus controladores.


6. Symfony Flex y las recipes

📘 Concepto: Symfony Flex es un plugin de Composer. Cada vez que instalas o eliminas un paquete, Flex comprueba si existe una recipe (receta) para él en el repositorio oficial (symfony/recipes) o en el de la comunidad (symfony/recipes-contrib) y la aplica.

Una recipe puede:

  • Registrar el bundle en config/bundles.php
  • Copiar ficheros de configuración en config/packages/
  • Añadir variables a .env (por ejemplo, DATABASE_URL o MAILER_DSN)
  • Añadir entradas a .gitignore
  • Crear carpetas (src/Entity/, templates/…)
  • Añadir servicios a compose.yaml

Alias

Flex permite usar alias cortos:

composer require twig        # symfony/twig-pack
composer require orm         # symfony/orm-pack
composer require mailer      # symfony/mailer
composer require --dev maker # symfony/maker-bundle
composer require --dev debug # symfony/debug-pack

📘 Concepto: Un pack es un paquete “vacío” que solo sirve para instalar varias dependencias de una vez. Por ejemplo, symfony/orm-pack instala Doctrine ORM, DoctrineBundle y Doctrine Migrations.

symfony.lock

Flex guarda en symfony.lock qué recipe (y qué versión de ella) se aplicó a cada paquete. Debe versionarse en Git para que todo el equipo tenga el mismo estado.

# Ver las recipes instaladas y si tienen actualizaciones
composer recipes

# Ver los detalles de una
composer recipes symfony/framework-bundle

# Actualizar una recipe (te mostrará los cambios para que los revises con Git)
composer recipes:update symfony/framework-bundle

💡 Consejo: Cuando actualices de versión de Symfony (por ejemplo de 8.1 a 8.2), ejecuta composer recipes:update paquete a paquete y revisa el git diff: así incorporas los cambios de configuración recomendados.


7. Entornos y ficheros .env

Los tres entornos

📘 Concepto: Un entorno es un conjunto de configuración. La misma aplicación se comporta distinto según el entorno, que se controla con la variable APP_ENV.

Entorno Uso Características
dev Desarrollo en tu ordenador Errores detallados, Web Debug Toolbar, caché que se regenera sola, Maker Bundle
test Ejecución de tests automáticos Base de datos de pruebas, sin toolbar, emails que no se envían
prod Servidor de producción Máximo rendimiento, errores genéricos para el usuario, logs

La configuración específica de un entorno se escribe con la clave when@entorno:

# config/packages/framework.yaml
framework:
    secret: '%env(APP_SECRET)%'
    session: true

when@test:
    framework:
        test: true
        session:
            storage_factory_id: session.storage.factory.mock_file

Los ficheros .env

Symfony carga las variables de entorno desde varios ficheros, en este orden (los posteriores sobrescriben a los anteriores):

Fichero ¿Se versiona? Para qué
.env Sí Valores por defecto para todos (sin contraseñas reales)
.env.local No Tus valores locales: contraseña de tu BD, claves de API… (se ignora en el entorno test)
.env.dev, .env.test, .env.prod Sí Valores por defecto específicos de un entorno
.env.dev.local, .env.test.local… No Valores locales específicos de un entorno

⚠️ Importante: Las variables de entorno reales del sistema (las que define el servidor o Docker) tienen siempre prioridad sobre cualquier fichero .env. Y nunca subas contraseñas reales a .env: van en .env.local o, en producción, en los secrets de Symfony (módulo 15).

Ejemplo de .env en el blog:

# .env
APP_ENV=dev
APP_SECRET=
APP_SHARE_DIR=var/share
DEFAULT_URI=http://localhost

DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
MAILER_DSN=null://null

Y tu configuración personal:

# .env.local (no se sube a Git)
DATABASE_URL="mysql://root:mi_password@127.0.0.1:3306/blog?serverVersion=8.4.0&charset=utf8mb4"

Para ver qué valor final tiene cada variable y de qué fichero sale:

php bin/console debug:dotenv

💡 Consejo: El APP_SECRET de desarrollo se genera automáticamente en .env.dev al crear el proyecto. En producción debes definir uno propio, largo y aleatorio.


8. La consola: bin/console

📘 Concepto: bin/console es la herramienta de línea de comandos de tu aplicación. Desde ella creas código, limpias la caché, ejecutas migraciones, depuras rutas y servicios, y lanzas tus propios comandos.

php bin/console            # lista todos los comandos disponibles
php bin/console list make  # lista solo los del grupo "make"
php bin/console help cache:clear   # ayuda de un comando

Con Symfony CLI puedes escribir symfony console en lugar de php bin/console. La ventaja es que usa la versión de PHP del proyecto y inyecta automáticamente las variables de los contenedores Docker (como DATABASE_URL).

Comandos que usarás a diario

Comando Para qué
about Información del proyecto: versión de Symfony, PHP, entorno, rutas de caché y logs
cache:clear Vacía la caché del entorno actual (--env=prod para producción)
debug:router Lista todas las rutas de la aplicación
router:match /ruta Indica qué ruta coincide con una URL
debug:container Lista los servicios del contenedor (admite búsqueda: debug:container mailer)
debug:autowiring Lista los tipos que puedes inyectar con autowiring
debug:config framework Muestra la configuración actual de un bundle
config:dump-reference twig Muestra todas las opciones posibles de un bundle
debug:twig Lista filtros, funciones y variables globales de Twig
debug:dotenv Muestra las variables de entorno y su origen
lint:twig, lint:yaml, lint:container Comprueban errores de sintaxis y configuración
make:* Generadores de código (Maker Bundle)
doctrine:* Base de datos y migraciones
php bin/console about
 -------------------- ----------------------------------
  Symfony
 -------------------- ----------------------------------
  Version              8.1.x
  Long-Term Support    No
  End of maintenance   01/2027
  End of life          01/2027
 -------------------- ----------------------------------
  Kernel
 -------------------- ----------------------------------
  Type                 App\Kernel
  Environment          dev
  Debug                true
  ...
 -------------------- ----------------------------------
  PHP
 -------------------- ----------------------------------
  Version              8.5.x
  ...

¿Cuándo limpiar la caché?

En dev, Symfony detecta los cambios en tus ficheros y regenera la caché automáticamente. Aun así, si algo “raro” ocurre tras instalar un paquete o cambiar configuración:

php bin/console cache:clear

💡 Consejo: Si cache:clear falla porque la propia caché está corrupta, puedes borrar la carpeta a mano: rm -rf var/cache/* (en PowerShell: Remove-Item -Recurse -Force var\cache\*).


9. Web Debug Toolbar y Profiler

En el entorno dev, cada página HTML muestra en la parte inferior la Web Debug Toolbar, una barra con información de la petición:

Panel Qué te dice
Código de estado y ruta Código HTTP, nombre de la ruta y controlador ejecutado
Tiempo y memoria Cuánto tardó la página y cuánta memoria consumió
Logs Mensajes de log, avisos y deprecations
Doctrine Número de consultas SQL y su tiempo (con un icono de alerta si hay demasiadas)
Twig Plantillas renderizadas y tiempo de renderizado
Seguridad Usuario autenticado y sus roles
Formularios, Mailer, Messenger… Información de cada componente usado

Al hacer clic en cualquier panel se abre el Profiler (/_profiler), que guarda el historial detallado de las últimas peticiones: incluso de las peticiones AJAX, las redirecciones o las llamadas a tu API.

dump() y dd()

El componente VarDumper te da dos funciones mucho mejores que var_dump():

public function index(): Response
{
    $articles = ['Hola Symfony', 'Rutas y controladores', 'Twig a fondo'];

    dump($articles);   // aparece en la toolbar (icono de la mira)
    // dd($articles);  // "dump and die": muestra y detiene la ejecución

    return $this->render('home/index.html.twig', ['articles' => $articles]);
}

⚠️ Importante: La toolbar solo aparece si la respuesta es HTML completo (con <body>). Si devuelves new Response('Hola') sin etiquetas HTML, no se mostrará. El Profiler, en cambio, sí registra la petición.


10. Maker Bundle

📘 Concepto: Maker Bundle es un generador de código para el entorno de desarrollo. En lugar de escribir a mano el esqueleto de un controlador, una entidad o un formulario, lo generas con un comando y luego lo adaptas.

Con --webapp ya está instalado. Si no:

composer require --dev symfony/maker-bundle

Los makers más usados

Comando Genera Módulo
make:controller Controlador + plantilla Twig 02
make:twig-extension Extensión de Twig (filtros/funciones) 03
make:twig-component Twig Component 03
make:stimulus-controller Controlador de Stimulus 04
make:entity Entidad Doctrine y su repositorio (o añade campos) 05
make:migration Migración a partir de los cambios en las entidades 05
make:crud CRUD completo: controlador, formulario y plantillas 05
make:fixtures Clase de datos de prueba 06
make:form Clase de formulario 07
make:validator Restricción de validación propia 08
make:user Entidad de usuario para la seguridad 10
make:security:form-login Login con formulario 10
make:registration-form Formulario de registro 10
make:voter Voter de seguridad 10
make:listener Event listener o subscriber 11
make:message Mensaje y handler de Messenger 11
make:test Test unitario o funcional 13
make:command Comando de consola 14
php bin/console list make   # lista completa en tu versión

11. Base de datos para desarrollo

El blog necesitará una base de datos a partir del módulo 05. Tienes dos opciones.

Opción A: Docker (recomendada)

Al instalar Doctrine (incluido en --webapp), su recipe añade un servicio de PostgreSQL al fichero compose.yaml:

# compose.yaml (generado por la recipe de Doctrine)
services:
###> doctrine/doctrine-bundle ###
  database:
    image: postgres:${POSTGRES_VERSION:-16}-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-app}
      # You should definitely change the password in production
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
      POSTGRES_USER: ${POSTGRES_USER:-app}
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
      timeout: 5s
      retries: 5
      start_period: 60s
    volumes:
      - database_data:/var/lib/postgresql/data:rw
###< doctrine/doctrine-bundle ###

volumes:
###> doctrine/doctrine-bundle ###
  database_data:
###< doctrine/doctrine-bundle ###

Y en compose.override.yaml (solo para desarrollo) expone el puerto:

# compose.override.yaml
services:
###> doctrine/doctrine-bundle ###
  database:
    ports:
      - "5432"
###< doctrine/doctrine-bundle ###

📘 Concepto: Los comentarios ###> paquete ### y ###< paquete ### marcan el bloque que gestiona la recipe. Flex los usa para saber qué eliminar si desinstalas el paquete. No los borres.

Arranca el contenedor:

docker compose up -d
docker compose ps        # comprueba que "database" está "healthy"

💡 Consejo: La línea "5432" (sin puerto del host) hace que Docker asigne un puerto aleatorio. No pasa nada: si usas symfony server:start y symfony console, Symfony CLI detecta el contenedor y define DATABASE_URL automáticamente con el puerto correcto. Compruébalo con symfony var:export --multiline.

¿Prefieres MySQL o MariaDB en Docker?

Puedes sustituir el servicio por este:

# compose.yaml (alternativa con MySQL)
services:
  database:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: blog
      MYSQL_USER: app
      MYSQL_PASSWORD: '!ChangeMe!'
      MYSQL_ROOT_PASSWORD: root
    ports:
      - "3306:3306"
    volumes:
      - database_data:/var/lib/mysql

volumes:
  database_data:

Opción B: MySQL / MariaDB instalados en local

Si ya tienes MySQL o MariaDB (por ejemplo con XAMPP, Laragon o el instalador oficial), solo tienes que configurar DATABASE_URL en .env.local:

# .env.local — MySQL
DATABASE_URL="mysql://root:@127.0.0.1:3306/blog?serverVersion=8.4.0&charset=utf8mb4"

# .env.local — MariaDB
# DATABASE_URL="mysql://root:@127.0.0.1:3306/blog?serverVersion=11.4.2-MariaDB&charset=utf8mb4"

# .env.local — SQLite (sin servidor: un fichero en var/)
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"

⚠️ Importante: Indica siempre serverVersion con la versión real de tu servidor. Doctrine la usa para generar el SQL adecuado; si no coincide, las migraciones pueden salir distintas en cada máquina.

Comprueba la conexión creando la base de datos:

php bin/console doctrine:database:create
# Created database "blog" for connection named default

12. Configurar el editor

PhpStorm

  • Instala el plugin Symfony Support (Settings → Plugins → Marketplace). Activa el plugin para el proyecto cuando te lo sugiera.
  • Te da autocompletado de nombres de rutas, plantillas, servicios y parámetros; navegación con Ctrl+clic desde render('article/show.html.twig') a la plantilla; y soporte para Twig y YAML.
  • Marca var/ como Excluded para que no indexe la caché.
  • Configura el intérprete de PHP (Settings → PHP) con la versión 8.4 o superior.

Visual Studio Code

Extensiones recomendadas:

Extensión Para qué
PHP Intelephense Autocompletado, navegación y análisis de PHP
Una extensión de Twig (resaltado y formato) Sintaxis de plantillas .twig
YAML (Red Hat) Valida los YAML de configuración usando los esquemas que Symfony declara al inicio de services.yaml y routes.yaml
PHP Debug (Xdebug) Depuración paso a paso
EditorConfig Respeta el .editorconfig que genera Symfony

💡 Consejo: Fíjate en la primera línea de config/services.yaml: # yaml-language-server: $schema=.... Esa línea permite que la extensión YAML de VS Code te avise de errores y te autocomplete opciones mientras escribes.


13. Tu primer controlador: “Hola mundo”

Paso 1: generar el controlador

php bin/console make:controller Home
 created: src/Controller/HomeController.php
 created: templates/home/index.html.twig

  Success!

 Next: Open your new controller class and add some pages!

Paso 2: revisar el código generado

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class HomeController extends AbstractController
{
    #[Route('/home', name: 'app_home')]
    public function index(): Response
    {
        return $this->render('home/index.html.twig', [
            'controller_name' => 'HomeController',
        ]);
    }
}

Paso 3: adaptarlo a la portada del blog

Cambia la ruta a / y pasa algunos datos a la plantilla:

<?php
// src/Controller/HomeController.php
declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class HomeController extends AbstractController
{
    #[Route('/', name: 'app_home')]
    public function index(): Response
    {
        // De momento, datos de ejemplo. En el módulo 05 vendrán de la base de datos.
        $articles = [
            'Hola, Symfony 8',
            'Rutas y controladores con atributos',
            'Plantillas Twig desde cero',
        ];

        return $this->render('home/index.html.twig', [
            'blog_name' => 'Blog de Symfony',
            'articles' => $articles,
        ]);
    }
}

Paso 4: la plantilla

{# templates/home/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}{{ blog_name }}{% endblock %}

{% block body %}
    <h1>¡Hola mundo desde {{ blog_name }}!</h1>

    <h2>Últimos artículos</h2>
    <ul>
        {% for title in articles %}
            <li>{{ title }}</li>
        {% endfor %}
    </ul>
{% endblock %}

Visita https://127.0.0.1:8000/: verás el título, la lista de artículos y la Web Debug Toolbar abajo.

Paso 5: comprobar la ruta

php bin/console debug:router
 -------------------------- -------- -------- ------ -----------------------------------
  Name                       Method   Scheme   Host   Path
 -------------------------- -------- -------- ------ -----------------------------------
  _preview_error             ANY      ANY      ANY    /_error/{code}.{_format}
  _wdt                       ANY      ANY      ANY    /_wdt/{token}
  _profiler_home             ANY      ANY      ANY    /_profiler/
  ...
  app_home                   ANY      ANY      ANY    /
 -------------------------- -------- -------- ------ -----------------------------------

Un controlador aún más simple

Un controlador no necesita plantilla. Basta con devolver un objeto Response:

<?php
// src/Controller/HelloController.php
declare(strict_types=1);

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class HelloController
{
    #[Route('/hola/{nombre}', name: 'app_hello')]
    public function hello(string $nombre = 'mundo'): Response
    {
        return new Response(sprintf(
            '<html><body><h1>¡Hola, %s!</h1></body></html>',
            htmlspecialchars($nombre)
        ));
    }
}

/hola/Ana → “¡Hola, Ana!”. /hola → “¡Hola, mundo!”.

✏️ Ejercicio: Crea la ruta /hola/{nombre} y prueba qué ocurre si quitas htmlspecialchars() y visitas /hola/<b>Ana</b>. Así entenderás por qué Twig escapa todo por defecto.

Errores comunes al empezar

Error Causa Solución
No route found for "GET /home" La ruta no existe o tiene otro path Revisa debug:router
Unable to find template "home/index.html.twig" La plantilla no está en templates/ o el nombre no coincide Comprueba la ruta y las mayúsculas
The controller must return a "Response" object El método no devuelve nada o devuelve un array Devuelve $this->render(...), $this->json(...) o una Response
Página en blanco sin toolbar La respuesta no es HTML completo Usa una plantilla que extienda base.html.twig
Could not open input file: bin/console No estás en la carpeta del proyecto Haz cd blog
Port 8000 is already in use Ya hay un servidor en marcha symfony server:stop o symfony server:start --port=8001

14. Ejercicios

Ejercicio 1: Entorno preparado

Instala PHP 8.4+ (mejor 8.5), Composer y Symfony CLI en tu sistema. Ejecuta symfony check:requirements y resuelve todas las recomendaciones, incluida la extensión intl. Anota la ruta de tu php.ini.

Ejercicio 2: Dos proyectos, dos sabores

Crea dos proyectos: prueba-skeleton con symfony new prueba-skeleton y prueba-webapp con symfony new prueba-webapp --webapp. Compara: carpetas creadas, número de bundles en config/bundles.php, ficheros en config/packages/ y tamaño de vendor/. Escribe una tabla con las diferencias.

Ejercicio 3: Flex en acción

En prueba-skeleton, ejecuta composer require twig y observa con git status y git diff qué ficheros ha creado o modificado la recipe. Después ejecuta composer remove twig y comprueba que Flex deshace los cambios.

Ejercicio 4: El proyecto del blog

Crea el proyecto definitivo blog con --webapp, arráncalo con HTTPS en segundo plano y ábrelo con symfony open:local. Levanta la base de datos con docker compose up -d (o configura tu MySQL local en .env.local) y ejecuta symfony console doctrine:database:create.

Ejercicio 5: Explorando la consola

Ejecuta y anota para qué sirve la salida de: about, debug:router, debug:autowiring --all, debug:config twig, debug:dotenv y debug:container --parameters. ¿Qué entorno y qué versión de PHP reporta about?

Ejercicio 6: Hola mundo del blog

Con make:controller, crea HomeController con la ruta / (nombre app_home) que muestre el nombre del blog y una lista de cinco títulos de artículos. Usa dump() para inspeccionar el array en la Web Debug Toolbar y abre el Profiler para ver qué plantillas se han renderizado.

Ejercicio 7: Variables de entorno

Añade en .env la variable BLOG_NAME="Mi blog" y en .env.local sobrescríbela con tu nombre. Usa debug:dotenv para comprobar qué valor gana. (En el módulo 09 aprenderás a inyectarla en tus servicios.)


Resumen

Concepto Comando / detalle
Requisitos PHP >= 8.4, Composer 2, extensiones ctype, iconv, pcre, session, simplexml, tokenizer (+ intl, pdo_*)
Instalar Symfony CLI scoop install symfony-cli / brew install symfony-cli/tap/symfony-cli / apt install symfony-cli
Comprobar requisitos symfony check:requirements
Proyecto web symfony new blog --webapp
Equivalente Composer composer create-project symfony/skeleton blog + composer require webapp
Servidor local symfony server:start, -d, symfony open:local, symfony server:stop
HTTPS local symfony server:ca:install (una vez)
Código propio src/ (namespace App\) y templates/
Raíz web public/ (front controller index.php)
Flex Plugin de Composer que aplica recipes; estado en symfony.lock
Entornos dev, test, prod (APP_ENV), configuración con when@entorno
Ficheros .env .env (versionado) → .env.local (privado) → .env.{entorno} → .env.{entorno}.local
Consola php bin/console o symfony console
Depurar debug:router, debug:container, debug:autowiring, about, cache:clear
Toolbar y Profiler Solo en dev; /_profiler; dump() y dd()
Maker Bundle make:controller, make:entity, make:crud…
Base de datos docker compose up -d o DATABASE_URL en .env.local