URLs limpias con Sluggable
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeUtilizar un ID de base de datos en tu URL es... un poco cutre. Es más habitual utilizar slugs. Un slug es una versión segura para la URL del nombre o título de un elemento. En este caso, el título de nuestra mezcla.
Para que esto sea posible, sólo tenemos que hacer una cosa: dar a nuestra clase VinylMix
una propiedad slug
que contenga esta cadena segura para la URL. Entonces, será súper fácil consultarla. El único truco es que... algo tiene que mirar el título de la mezcla y establecer esa propiedad slug
cada vez que se guarde una mezcla. Y, lo ideal sería que eso ocurriera automáticamente... porque me siento un poco perezoso... y no quiero hacer ese trabajo manualmente en todas partes. Pues bien, ese es el trabajo del comportamiento ralentizable de las extensiones de Doctrine.
Activar la escucha lenta
Vuelve a config/packages/stof_doctrine_extensions.yaml
y añadesluggable: true
// ... lines 1 - 2 | |
stof_doctrine_extensions: | |
// ... line 4 | |
orm: | |
default: | |
// ... line 7 | |
sluggable: true |
Una vez más, esto habilita un oyente que mirará cada entidad, cada vez que se guarde una, para ver si el comportamiento sluggable está activado en ella. ¿Cómo lo hacemos?
Añadiendo la propiedad Slug
En primer lugar, necesitamos una propiedad slug
en nuestra entidad. Para añadirla, en tu terminal, ejecuta
php bin/console make:entity
Actualizar VinylMix
para añadir un nuevo campo slug
. Será una cadena, y limitémosla a 100 caracteres. Haz también que no sea anulable: debe ser obligatorio en la base de datos. Y ya está Pulsa "intro" una vez más para terminar.
Esto, como es lógico, ha añadido una propiedad slug
... además de los métodos getSlug()
y setSlug()
en la parte inferior.
// ... lines 1 - 10 | |
class VinylMix | |
{ | |
// ... lines 13 - 34 | |
#[ORM\Column(length: 100)] | |
private ?string $slug = null; | |
// ... lines 37 - 128 | |
public function getSlug(): ?string | |
{ | |
return $this->slug; | |
} | |
public function setSlug(string $slug): self | |
{ | |
$this->slug = $slug; | |
return $this; | |
} | |
} |
Una cosa que el comando make:entity
no te pregunta es si quieres que la propiedad sea única en la base de datos. En el caso de slug
, sí queremos que sea única, así que añade unique: true
. Eso añadirá una restricción unique
en la base de datos para asegurarnos de que nunca tengamos duplicados.
// ... lines 1 - 10 | |
class VinylMix | |
{ | |
// ... lines 13 - 34 | |
#[ORM\Column(length: 100, unique: true)] | |
private ?string $slug = null; | |
// ... lines 37 - 139 | |
} |
Antes de pensar en cualquier magia de la babosa, genera una migración para la nueva propiedad:
symfony console make:migration
Como siempre, abriré ese nuevo archivo para asegurarme de que se ve bien. Y... ¡lo está! Añade slug
incluyendo un UNIQUE INDEX
para slug
. Y cuando lo ejecutamos con
symfony console doctrine:migrations:migrate
explota... por la misma razón que la última vez: Not null violation
. Estamos añadiendo una nueva columna slug
a nuestra tabla que no es nula... lo que significa que cualquier registro existente no funcionará. Como dije en el capítulo anterior, si tu base de datos ya está en producción, tendrías que arreglar esto. Pero como la nuestra no lo está, podemos hacer trampa y reiniciar la base de datos como hicimos antes:
symfony console doctrine:database:drop --force
Entonces:
symfony console doctrine:database:create
Por último, vuelve a ejecutar todas las migraciones desde el principio:
symfony console doctrine:migrations:migrate
Y... ¡sí! 4 migraciones ejecutadas.
Añadir el atributo Sluggable
Llegados a este punto, hemos activado el oyente sluggable y hemos añadido una columna slug
. Pero aún nos falta un paso. Lo probaré yendo a /mix/new
y... error:
[...] la columna "slug" de la relación "vinyl_mix" viola la restricción not-null.
Sí Todavía no hay nada que establezca la propiedad slug
. Para indicar a la biblioteca de extensiones que se trata de una propiedad slug
que debe establecer automáticamente, tenemos que añadir -sorpresa- un atributo Se llama #[Slug]
. Pulsa "tab" para autocompletarlo, lo que añadirá la declaración use
que necesitas en la parte superior. A continuación, di fields
, que se establece en una matriz, y dentro, sólo title
.
// ... lines 1 - 7 | |
use Gedmo\Mapping\Annotation\Slug; | |
// ... lines 9 - 11 | |
class VinylMix | |
{ | |
// ... lines 14 - 36 | |
fields: ['title']) | (|
private ?string $slug = null; | |
// ... lines 39 - 141 | |
} |
Esto dice:
utiliza el campo "título" para generar este slug.
Y ahora... ¡parece que funciona! Si comprobamos la base de datos...
symfony console doctrine:query:sql 'SELECT * FROM vinyl_mix'
¡Woohoo! El slug
está aquí abajo... y puedes ver que la biblioteca también es lo suficientemente inteligente como para añadir un poco de -1
, -2
, -3
para mantenerlo único.
Actualizando nuestra ruta para usar {slug}
Ahora que tenemos esta columna slug
, en MixController
, vamos a hacer que nuestra ruta sea más moderna utilizando {slug}
.
// ... lines 1 - 12 | |
class MixController extends AbstractController | |
{ | |
// ... lines 15 - 35 | |
'/mix/{slug}', name: 'app_mix_show') | (|
public function show(VinylMix $mix): Response | |
// ... lines 38 - 60 | |
} |
¿Qué más tenemos que cambiar aquí? Nada Como el comodín de la ruta se llama ahora {slug}
, Doctrine utilizará este valor para consultar la propiedad slug
. ¡Genial!
Actualizar los enlaces a la ruta
Sin embargo, tenemos que actualizar los enlaces que generemos a esta ruta. Observa: copia el nombre de la ruta - app_mix_show
- y busca dentro de este archivo. ¡Sí! Lo utilizamos aquí abajo para redirigirnos después de votar. Ahora, en lugar de pasar el comodín id
, pasa slug
ajustado a $mix->getSlug()
.
// ... lines 1 - 12 | |
class MixController extends AbstractController | |
{ | |
// ... lines 15 - 44 | |
public function vote(VinylMix $mix, Request $request, EntityManagerInterface $entityManager): Response | |
{ | |
// ... lines 47 - 56 | |
return $this->redirectToRoute('app_mix_show', [ | |
'slug' => $mix->getSlug(), | |
]); | |
} | |
} |
Y si has buscado, hay otro lugar donde generamos una URL a esta ruta:templates/vinyl/browse.html.twig
. Aquí mismo, tenemos que cambiar el enlace de la página "Examinar" a slug: mix.slug
.
// ... lines 1 - 2 | |
{% block body %} | |
// ... lines 4 - 28 | |
{% for mix in mixes %} | |
<div class="col col-md-4"> | |
<a href="{{ path('app_mix_show', { | |
slug: mix.slug | |
}) }}" class="mixed-vinyl-container p-3 text-center"> | |
// ... lines 34 - 42 | |
</a> | |
</div> | |
{% endfor %} | |
// ... lines 46 - 48 | |
{% endblock %} |
¡Hora de probar! Déjame refrescar un par de veces... luego vuelve a la página de inicio... haz clic en "Examinar mezclas", y... ¡ahí está nuestra lista! Si hacemos clic en una de estas mezclas... ¡hermoso! Ha utilizado el slug y ha consultado a través del slug. La vida es buena.
Vale, ahora, para añadir datos ficticios y poder utilizar el sitio, hemos creado esta acción new
. Pero esa es una forma bastante pobre de manejar datos ficticios: es manual, requiere refrescar la página y, aunque tenemos algo de aleatoriedad, ¡crea datos aburridos!
Así que, a continuación, vamos a añadir un sistema de fijación de datos adecuado para remediarlo.
Slug behavior didn't work when I experimented in PHP 7, below is my code:
The config is:
`stof_doctrine_extensions:
Services.yaml config is:
`parameters:
services:
`
Error:
Controller "App\Controller\ProductController::show" requires the "$product" argument that could not be resolved. Cannot autowire argument $product of "App\Controller\ProductController::show()": it needs an instance of "App\Entity\Product" but this type has been excluded in "config/services.yaml".