Métodos del modelo inteligente y dinamización del diseño
Añadir el .value
al final del enum para imprimirlo funciona a las mil maravillas. Pero quiero mostrar otra solución más elegante.
Añadir métodos de modelo inteligentes
En Starship
, probablemente será habitual que queramos obtener el estado de la cadena de un Starship
. Para facilitarlo, ¿por qué no añadir aquí un método abreviado llamadogetStatusString()
? Éste devolverá un string
, y dentro, devolverá $this->status->value
.
// ... lines 1 - 4 | |
class Starship | |
{ | |
// ... lines 7 - 40 | |
public function getStatusString(): string | |
{ | |
return $this->status->value; | |
} | |
} |
Gracias a esto, en la plantilla, podemos acortar a ship.statusString
.
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between"> | |
<div class="flex justify-center min-[1174px]:justify-start"> | |
// ... line 18 | |
<div class="ml-5"> | |
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10"> | |
// ... line 21 | |
<p class="uppercase text-xs text-nowrap">{{ ship.statusString }}</p> | |
</div> | |
// ... lines 24 - 29 | |
</div> | |
</div> | |
// ... lines 32 - 42 | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Ah, ¡y esto es más inteligencia Twig! ¡No hay ninguna propiedad llamada statusString
en Starship
! Pero a Twig no le importa. Ve que hay un método getStatusString()
y lo llama.
Observa: cuando actualizamos, la página sigue funcionando. Me gusta mucho esta solución, así que la copiaré... y la repetiré aquí arriba para el atributo alt
.
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between"> | |
<div class="flex justify-center min-[1174px]:justify-start"> | |
<img class="h-[83px] w-[84px]" src="/images/status-in-progress.png" alt="Status: {{ ship.statusString }}"> | |
<div class="ml-5"> | |
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10"> | |
// ... line 21 | |
<p class="uppercase text-xs text-nowrap">{{ ship.statusString }}</p> | |
</div> | |
// ... lines 24 - 29 | |
</div> | |
</div> | |
// ... lines 32 - 42 | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Y mientras arreglamos esto, en show.html.twig
, imprimiremos el estado allí también. Así que haré ese mismo cambio... y luego cerraré esto.
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 11 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 13 - 15 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10"> | |
// ... line 20 | |
<p class="uppercase text-xs">{{ ship.statusString }}</p> | |
</div> | |
// ... lines 23 - 34 | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
Terminando nuestra Plantilla Dinámica
Bien: vamos a terminar de hacer dinámica nuestra plantilla de página de inicio: a partir de aquí todo es coser y cantar. Para el nombre del barco, {{ ship.name }}
, para el capitán, {{ ship.captain }}
. Y aquí abajo para la clase, {{ ship.class }}
.
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between"> | |
<div class="flex justify-center min-[1174px]:justify-start"> | |
// ... line 18 | |
<div class="ml-5"> | |
// ... lines 20 - 23 | |
<h4 class="text-[22px] pt-1 font-semibold"> | |
<a | |
// ... lines 26 - 27 | |
>{{ ship.name }}</a> | |
</h4> | |
</div> | |
</div> | |
<div class="flex justify-center min-[1174px]:justify-start mt-2 min-[1174px]:mt-0 shrink-0"> | |
<div class="border-r border-white/20 pr-8"> | |
<p class="text-slate-400 text-xs">Captain</p> | |
<p class="text-xl">{{ ship.captain }}</p> | |
</div> | |
<div class="pl-8 w-[100px]"> | |
<p class="text-slate-400 text-xs">Class</p> | |
<p class="text-xl">{{ ship.class }}</p> | |
</div> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Ah, y rellenemos el enlace: {{ path() }}
y luego el nombre de la ruta. Estamos enlazando con la página del espectáculo del barco, así que la ruta es app_starship_show
. Y como esto tiene un comodín id
, pasa id
a ship.id
.
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between"> | |
<div class="flex justify-center min-[1174px]:justify-start"> | |
// ... line 18 | |
<div class="ml-5"> | |
// ... lines 20 - 23 | |
<h4 class="text-[22px] pt-1 font-semibold"> | |
<a | |
class="hover:text-slate-200" | |
href="{{ path('app_starship_show', { id: ship.id }) }}" | |
>{{ ship.name }}</a> | |
</h4> | |
</div> | |
</div> | |
// ... lines 32 - 42 | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Y ahora, ¡mucho mejor! Se ve bien y podemos hacer clic en estos enlaces.
Rutas de imagen dinámicas
Pero... la imagen sigue rota. Antes, cuando copiamos las imágenes en nuestro directorioassets/
, incluí tres archivos para los tres estados. Aquí arriba, estamos apuntando "más o menos" al estado en curso... pero ésta no es la forma correcta de referenciar imágenes en el directorio assets/
. En su lugar, di {{ asset() }}
y pasa la ruta relativa al directorio assets/
, llamada ruta "lógica".
Si lo intentamos ahora... estamos más cerca. Pero la parte "en curso" tiene que ser dinámica en función del estado. Algo que podríamos intentar es la concatenación Twig: añadir ship.status
a la cadena. Eso es posible, aunque es un poco feo.
En lugar de eso, volvamos a la solución que utilizamos hace un momento: hacer que todos los datos sobre nuestro Starship
sean fácilmente accesibles... desde la clase Starship
.
Esto es lo que quiero decir: añade un public function getStatusImageFilename()
que devuelva una cadena.
// ... lines 1 - 4 | |
class Starship | |
{ | |
// ... lines 7 - 45 | |
public function getStatusImageFilename(): string | |
{ | |
// ... lines 48 - 52 | |
} | |
} |
Vamos a hacer toda la lógica para crear el nombre de archivo aquí mismo. Pondré una función match
.
Esto dice: comprueba $this->status
y si es igual a WAITING
, devuelve esta cadena. Si es igual a IN_PROGRESS
devuelve esta cadena y así sucesivamente.
// ... lines 1 - 4 | |
class Starship | |
{ | |
// ... lines 7 - 45 | |
public function getStatusImageFilename(): string | |
{ | |
return match ($this->status) { | |
StarshipStatusEnum::WAITING => 'images/status-waiting.png', | |
StarshipStatusEnum::IN_PROGRESS => 'images/status-in-progress.png', | |
StarshipStatusEnum::COMPLETED => 'images/status-complete.png', | |
}; | |
} | |
} |
Y exactamente igual que antes, como tenemos un método getStatusImageFilename()
, podemos, en Twig, hacer como si tuviéramos una propiedad statusImageFilename
.
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 13 | |
<div class="space-y-5"> | |
{% for ship in ships %} | |
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between"> | |
<div class="flex justify-center min-[1174px]:justify-start"> | |
// ... line 18 | |
<div class="ml-5"> | |
// ... lines 20 - 23 | |
<h4 class="text-[22px] pt-1 font-semibold"> | |
<a | |
class="hover:text-slate-200" | |
href="{{ path('app_starship_show', { id: ship.id }) }}" | |
>{{ ship.name }}</a> | |
</h4> | |
</div> | |
</div> | |
// ... lines 32 - 42 | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 46 - 50 | |
</div> | |
</main> | |
{% endblock %} |
Y ahora, ¡ya lo tenemos!
Últimos detalles para dinamizar el diseño
¡Últimos detalles! Rellenemos algunos enlaces que faltan, como este logotipo que debería ir a la página de inicio. Ahora mismo... no va a ninguna parte.
Recuerda que cuando queremos enlazar a una página, tenemos que asegurarnos de que esa ruta tiene un nombre. En src/Controller/MainController.php
... nuestra página de inicio no tiene nombre. Vale, tiene un nombre autogenerado, pero no queremos confiar en eso.
Añade name:
ajustado a app_homepage
. O puedes utilizar app_main_homepage
.
// ... lines 1 - 9 | |
class MainController extends AbstractController | |
{ | |
'/', name: 'app_homepage') | (|
public function homepage(StarshipRepository $starshipRepository): Response | |
// ... lines 14 - 22 | |
} |
Para enlazar el logo, en base.html.twig
... aquí está... Utiliza {{ path('app_homepage') }}
.
<html> | |
// ... lines 3 - 13 | |
<body class="text-white" style="background: radial-gradient(102.21% 102.21% at 50% 28.75%, #00121C 42.62%, #013954 100%);"> | |
<div class="flex flex-col justify-between min-h-screen relative"> | |
<div> | |
<header class="h-[114px] shrink-0 flex flex-col sm:flex-row items-center sm:justify-between py-4 sm:py-0 px-6 border-b border-white/20 shadow-md"> | |
<a href="{{ path('app_homepage') }}"> | |
// ... line 19 | |
</a> | |
// ... lines 21 - 34 | |
</header> | |
// ... line 36 | |
</div> | |
// ... lines 38 - 40 | |
</div> | |
</body> | |
</html> |
Cópialo y repítelo a continuación para otro enlace de inicio.
<html> | |
// ... lines 3 - 13 | |
<body class="text-white" style="background: radial-gradient(102.21% 102.21% at 50% 28.75%, #00121C 42.62%, #013954 100%);"> | |
<div class="flex flex-col justify-between min-h-screen relative"> | |
<div> | |
<header class="h-[114px] shrink-0 flex flex-col sm:flex-row items-center sm:justify-between py-4 sm:py-0 px-6 border-b border-white/20 shadow-md"> | |
<a href="{{ path('app_homepage') }}"> | |
// ... line 19 | |
</a> | |
<nav class="flex space-x-4 font-semibold"> | |
<a class="hover:text-amber-400 pt-2" href="{{ path('app_homepage') }}"> | |
Home | |
</a> | |
// ... lines 25 - 33 | |
</nav> | |
</header> | |
// ... line 36 | |
</div> | |
// ... lines 38 - 40 | |
</div> | |
</body> | |
</html> |
Dejaremos estos otros enlaces para un futuro tutorial.
De vuelta al navegador, ¡haz clic en ese logotipo! Ya está. El último enlace que falta está en la página del programa. Este enlace "atrás" también debería ir a la página de inicio. Abreshow.html.twig
. Y arriba -ahí está- pegaré ese mismo enlace.
// ... lines 1 - 4 | |
{% block body %} | |
<div class="my-4 px-8"> | |
<a class="bg-white hover:bg-gray-200 rounded-xl p-2 text-black" href="{{ path('app_homepage') }}"> | |
<svg class="inline text-black" xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#000" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg> | |
Back | |
</a> | |
</div> | |
// ... lines 12 - 38 | |
{% endblock %} |
Ok equipo, ¡el diseño está hecho! ¡Enhorabuena! Regálate un té... o un café con leche... o un donut o un paseo por la naturaleza para celebrarlo. ¡Porque esto es enorme! Nuestro sitio parece y se siente real. Estoy encantada.
Ahora podemos centrarnos en los detalles más sutiles. Por ejemplo, cuando hacemos clic en este enlace, se supone que la barra lateral se colapsa. Para ello, quiero presentarte mi herramienta favorita para escribir JavaScript: Stimulus.
Thanks a lot for this really concise and well done tutorial!
Suggestion
File: templates/main/homepage.html.twig
Please change image src in line 18
from
<img class="h-[83px] w-[84px]" src="/images/status-in-progress.png" alt="Status: {{ ship.statusString }}">
to:
<img class="h-[83px] w-[84px]" src="{{ asset(ship.statusImageFilename) }}" alt="Status: {{ ship.statusString }}">
Comment:
This doesn't work for me:
<img class="h-[83px] w-[84px]" src="{{ ship.statusImageFilename }}" alt="Status: {{ ship.statusString }}">
Cheers
Uwe