Vamos a crear una "página de presentación" de barcos: una página que muestre los detalles de un solo barco. La página de inicio vive en MainController. Y así podríamos añadir otra ruta y método aquí. Pero a medida que mi sitio crezca, probablemente tendré varias páginas relacionadas con naves estelares: quizá para editarlas y eliminarlas. Así que, en lugar de eso, en el directorio Controller/, crea una nueva clase. Llámala StarshipController, y, como de costumbre, extiendeAbstractController.
Crear la página Mostrar
Dentro, ¡manos a la obra! Añade un public function llamado show(), yo añadiré el tipo de retorno Response, luego la ruta, con /starships/ y un comodín llamado {id}. Y de nuevo, es opcional, pero seré extravagante y añadiré el \d+ para que el comodín sólo coincida con un número.
Ahora, como tenemos un comodín {id}, se nos permite tener un argumento $id aquí abajo. dd($id) para ver cómo vamos hasta ahora.
| // ... lines 1 - 2 | |
| namespace App\Controller; | |
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |
| use Symfony\Component\HttpFoundation\Response; | |
| use Symfony\Component\Routing\Attribute\Route; | |
| class StarshipController extends AbstractController | |
| { | |
| ('/starships/{id<\d+>}') | |
| public function show(int $id): Response | |
| { | |
| dd($id); | |
| } | |
| } |
Pruébalo. Dirígete a /starships/2. ¡Estupendo!
Ahora vamos a hacer algo familiar: tomar este $id y consultar nuestra base de datos imaginaria en busca del Starship coincidente. La clave para hacerlo es nuestro servicio StarshipRepositoryy su útil método find().
En el controlador, añade un argumento StarshipRepository $repository... y luego di que$ship es igual a $repository->find($id). Y si no es $ship, activa una página 404 con los lanzamientos $this->createNotFoundException() y starship not found.
¡Genial! En la parte inferior, en lugar de devolver JSON, renderiza una plantilla: devuelve$this->render() y sigue la convención de nomenclatura estándar para plantillas:starship/show.html.twig. Pasa esta variable: $ship.
| // ... lines 1 - 4 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 6 - 9 | |
| class StarshipController extends AbstractController | |
| { | |
| ('/starships/{id<\d+>}') | |
| public function show(int $id, StarshipRepository $repository): Response | |
| { | |
| $ship = $repository->find($id); | |
| if (!$ship) { | |
| throw $this->createNotFoundException('Starship not found'); | |
| } | |
| return $this->render('starship/show.html.twig', [ | |
| 'ship' => $ship, | |
| ]); | |
| } | |
| } |
Crear la plantilla
Controlador, ¡comprobado! A continuación, en el directorio templates/, podríamos crear un directoriostarship/ y show.html.twig dentro. Pero quiero mostrarte un atajo del plugin Symfony PhpStorm. Haz clic en el nombre de la plantilla, pulsa Alt+Enter y... ¡fíjate! En la parte superior pone "Twig: Crear plantilla". Confirma la ruta y ¡boom! ¡Ya tenemos nuestra nueva plantilla! Está... escondida por aquí. Ahí está:starship/show.html.twig.
Prácticamente todas las plantillas empiezan igual: {% extend 'base.html.twig' %}... ¡luego anula algunos bloques! Anula title... y esta vez, utiliza la variableship: ship.name. Termina con endblock.
Y para el contenido principal, añade el bloque body... endblock y pon un h1dentro. Vuelve a imprimir ship.name y... Pegaré una tabla con algo de información.
| // ... lines 1 - 4 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 6 - 9 | |
| class StarshipController extends AbstractController | |
| { | |
| ('/starships/{id<\d+>}') | |
| public function show(int $id, StarshipRepository $repository): Response | |
| { | |
| $ship = $repository->find($id); | |
| if (!$ship) { | |
| throw $this->createNotFoundException('Starship not found'); | |
| } | |
| return $this->render('starship/show.html.twig', [ | |
| 'ship' => $ship, | |
| ]); | |
| } | |
| } |
Aquí no hay nada especial: sólo estamos imprimiendo datos básicos del barco.
Cuando probamos la página... ¡está viva!
Enlazar entre páginas
Siguiente pregunta: desde la página de inicio, ¿cómo podríamos añadir un enlace a la nueva página de presentación de barcos? La opción más obvia es codificar la URL, como /starships/ y luego el id. Pero hay una forma mejor. En lugar de eso, vamos a decirle a Symfony:
Oye, quiero generar una URL para esta ruta.
La ventaja es que si más adelante decidimos cambiar la URL de esta ruta, todos los enlaces a ella se actualizarán automáticamente.
Déjame que te lo muestre. Busca tu terminal y ejecuta:
php bin/console debug:router
Aún no lo he mencionado, pero cada ruta tiene un nombre interno. Ahora mismo, están siendo autogeneradas por Symfony, lo cual está bien. Pero en cuanto quieras generar una URL a una ruta, debemos tomar el control de ese nombre para asegurarnos de que nunca cambie.
Busca la ruta show page y añade una clave name. Yo utilizaré app_starship_show.
| // ... lines 1 - 9 | |
| class StarshipController extends AbstractController | |
| { | |
| ('/starships/{id<\d+>}', name: 'app_starship_show') | |
| public function show(int $id, StarshipRepository $repository): Response | |
| // ... lines 14 - 23 | |
| } |
El nombre podría ser cualquier cosa, pero ésta es la convención que yo sigo: app porque es una ruta que estoy creando en mi aplicación, y luego el nombre de la clase del controlador y el nombre del método.
Nombrar una ruta no cambia su funcionamiento. Pero sí nos permite generar una URL hacia ella. Abre templates/main/homepage.html.twig. Aquí abajo, convierte el nombre de la ruta en un enlace. Lo pondré en varias líneas y añadiré una etiqueta a conhref="". Para generar la URL, diré {{ path() }} y le pasaré el nombre de la ruta. Pondré la etiqueta de cierre en el otro lado.
Si nos detenemos ahora, esto no funcionará del todo. En la página de inicio:
Faltan algunos parámetros obligatorios -
id- para generar una URL para la rutaapp_starship_show.
¡Eso tiene sentido! Le estamos diciendo a Symfony:
¡Hola! Quiero generar una URL para esta ruta.
Symfony entonces responde:
Genial... excepto que esta ruta tiene un comodín. Así que... ¿qué quieres quieres que ponga en la URL para la parte
id?
Cuando hay un comodín en la ruta, tenemos que añadir un segundo argumento a path()con {}. Esta es la sintaxis de matriz asociativa de Twig. Es exactamente igual que JavaScript: es una lista de pares clave-valor. Pasa id ajustado a myShip.id.
| // ... lines 1 - 4 | |
| {% block body %} | |
| // ... lines 6 - 20 | |
| <div> | |
| // ... lines 22 - 23 | |
| <table> | |
| <tr> | |
| <th>Name</th> | |
| <td> | |
| <a href="{{ path('app_starship_show', { | |
| id: myShip.id | |
| }) }}">{{ myShip.name }}</a> | |
| </td> | |
| </tr> | |
| // ... lines 33 - 44 | |
| </table> | |
| </div> | |
| {% endblock %} |
Y ahora... ¡ya está! Mira esa URL: /starships/3.
Muy bien, nuestro sitio sigue siendo feo. Es hora de empezar a arreglarlo incorporando Tailwind CSS y aprendiendo sobre el componente AssetMapper de Symfony.
10 Comments
at the 2nd minute and 9 seconds: you show a cheatcode for your code editor. For other viewers, if you dont see the two options with the yellow lightbulb in phpstorm, try this: File -> Settings -> PHP -> Symfony -> Enable plugin.
it was disabled in my editor and enabling it did the trick! :)
Hey Linda,
Thanks for this tip! :)
Cheers!
What if I wanted to add a 'former command'* listing that referenced one of the other ships? How would I modify StarshipController to allow that? Do I just write another find with the other ship id as the variable?
Hey @skribe!
Yep, you're on the right track. For the app in its current state, you'd need to add the former ship id to the model, then use the repository to grab it in your controller.
Once the database/Doctrine is setup, this will be much easier. This is a perfect example of a Doctrine relationship and that's the next course to come out in this track! Keep an eye out for that - it should be dropping in the next few weeks.
--Kevin
Hi!
Using the same regex
<\d+>twice in 2 different places (StarshipControllerandStarshipApiController) is code duplication, does Symfony allow to reuse regex in the#[Route ...]attribute with an existing functional, like a service?Thanks!
Hey @randomsymbols!
Good point. The Routing component includes a Requirement enum that includes common regexes (
Requirement::DIGITScould be used here). Check out this example to see how it works. I like using this as it makes your routes easier to read/understand.You can also add your own constants (or backed enums) for commonly used patterns in your application.
I personally prefer using the
requirementsattribute argument vs embedding the regex in the path (again, to ease readability).Hope that helps!
Kevin
Hi, at the 2nd minute and 9 seconds: https://symfonycasts.com/screencast/symfony/generate-urls?playAt=127 - you show a cheatcode for your code editor, is there an extension or similar for VSCODE? THANKS :)
Hey Vince,
I see you're referring to creating a template feature from the Symfony plugin. Unfortunately, I don't use VSCode so can't say you for sure, but as I see over the internet there's no something similar there. You just need to create that file manually. What I see is that you can create a "custom snippet" in VS Code that makes it easier to generate basic Twig templates. This won't automatically generate the file, but it can streamline repetitive tasks for you I suppose.
I hope this helps!
Cheers!
Thanks for your response :)
I found the same information on the Internet but the task is not automatic, you must to enter the path, name, ext... well.. create the file is most quick. ^^
Hey Vince,
Yeah, it would be great to have better integration. But so far, Symfony plugin just creates an empty template, so empty file creating should not be much longer :) Otherwise, consider switching to PhpStorm ;)
Cheers!
"Houston: no signs of life"
Start the conversation!