Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Rutas comodín

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

La página de inicio será el lugar donde el usuario podrá diseñar y construir su próxima cinta de mezclas. Pero además de crear nuevas cintas, los usuarios también podrán explorar las creaciones de otras personas.

Crear una segunda página

Vamos a crear una segunda página para eso. ¿Cómo? Añadiendo un segundo controlador: función pública, qué tal browse: el nombre no importa realmente. Y para ser responsable, añadiré un tipo de retorno Response.

Por encima de esto, necesitamos nuestra ruta. Ésta será exactamente igual, salvo que pondremos la URL en /browse. Dentro del método, ¿qué es lo que siempre devolvemos de un controlador? Así es: ¡un objeto Response! Devuelve un nuevo Response... con un mensaje corto para empezar.

... lines 1 - 7
class VinylController
{
... lines 10 - 15
#[Route('/browse')]
public function browse(): Response
{
return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!');
}
}

¡Vamos a probarlo! Si actualizamos la página de inicio, no cambia nada. Pero si vamos a /browse... ¡lo machacamos! ¡Una segunda página en menos de un minuto! ¡Caramba!

En esta página, acabaremos por listar las cintas de mezclas de otros usuarios. Para ayudar a encontrar algo que nos guste, quiero que los usuarios también puedan buscar por género. Por ejemplo, si voy a /browse/death-metal, eso me mostraría todas las cintas de vinilo de death metal. Hardcore.

Por supuesto, si probamos esta URL ahora mismo... no funciona.

No se ha encontrado la ruta

No se han encontrado rutas coincidentes para esta URL, por lo que nos muestra una página 404. Por cierto, lo que estás viendo es la elegante página de excepciones de Symfony, porque estamos desarrollando. Nos da muchos detalles cuando algo va mal. Cuando finalmente despliegues a producción, puedes diseñar una página de error diferente que verían tus usuarios.

{Cartel de la muerte} Rutas

De todos modos, la forma más sencilla de hacer que esta URL funcione es simplemente... cambiar la URL a/browse/death-metal

... lines 1 - 7
class VinylController
{
... lines 10 - 15
#[Route('/browse/death-metal')]
public function browse(): Response
{
return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!');
}
}

Pero... no es súper flexible, ¿verdad? Necesitaríamos una ruta para cada género... ¡que podrían ser cientos! Y además, ¡acabamos de matar la URL /browse! Ahora es 404.

Lo que realmente queremos es una ruta que coincida con /browse/<ANYTHING>. Y podemos hacerlo con un comodín. Sustituye el código duro death-metal por {} y, dentro,slug. Slug es sólo una palabra técnica para designar un "nombre seguro para la URL". En realidad, podríamos haber puesto cualquier cosa dentro de las llaves, como {genre} o {coolMusicCategory}: no hay ninguna diferencia. Pero sea lo que sea que pongamos dentro de este comodín, se nos permite tener un argumento con ese mismo nombre: $slug.

... lines 1 - 7
class VinylController
{
... lines 10 - 15
#[Route('/browse/{slug}')]
public function browse(): Response
{
return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!');
}
}

Sí, si vamos a /browse/death-metal, coincidirá con esta ruta y pasará la cadenadeath-metal a ese argumento. La coincidencia se hace por nombre: {slug} conecta con $slug.

Para ver si funciona, devolvamos una respuesta diferente: Genre y luego la $slug.

... lines 1 - 7
class VinylController
{
... lines 10 - 15
#[Route('/browse/{slug}')]
public function browse($slug): Response
{
return new Response('Genre: '.$slug);
//return new Response('Breakup vinyl? Angsty 90s rock? Browse the collection!');
}
... lines 23 - 24

¡Hora de probar! Vuelve a /browse/death-metal y... ¡sí! Prueba con /browse/emo y ¡sí! ¡Estoy mucho más cerca de mi cinta de mezcla de Dashboard Confessional!

Ah, y es opcional, pero puedes añadir un tipo string al argumento $slug. Eso no cambia nada... es sólo una bonita forma de programar: el $slug ya iba a ser siempre una cadena.

... lines 1 - 7
class VinylController
{
... lines 10 - 15
#[Route('/browse/{slug}')]
public function browse(string $slug): Response
{
... lines 19 - 21
}
... lines 23 - 24

Un poco más adelante, aprenderemos cómo puedes convertir un comodín numérico -como el número 5- en un número entero si así lo deseas.

Usando el componente de cadena de Symfony

Hagamos esta página un poco más elegante. En lugar de imprimir el slug exactamente, vamos a convertirlo en un título. Digamos $title = str_replace() y sustituyamos los guiones por espacios. Luego, aquí abajo, utiliza el título en la respuesta. En un futuro tutorial, vamos a consultar la base de datos para estos géneros, pero, por ahora, al menos podemos hacer que tenga un aspecto más agradable.

... lines 1 - 7
class VinylController
{
... lines 10 - 15
#[Route('/browse/{slug}')]
public function browse(string $slug): Response
{
$title = str_replace('-', ' ', $slug);
return new Response('Genre: '.$title);
... line 23
}
... lines 25 - 26

Si lo probamos, el Emo no se ve diferente... pero el death metal sí. ¡Pero quiero que sea más elegante! Añade otra línea con $title = y luego escribe u y autocompleta una función que se llama literalmente... u.

No utilizamos muchas funciones de Symfony, pero éste es un ejemplo raro. Proviene de una biblioteca de Symfony llamada symfony/string. Como he mencionado, Symfony tiene muchas bibliotecas diferentes -también llamadas componentes- y vamos a aprovechar esas bibliotecas todo el tiempo. Esta te ayuda a hacer transformaciones de cadenas... y resulta que ya está instalada.

Mueve el str_replace() al primer argumento de u(). Esta función devuelve un objeto sobre el que podemos hacer operaciones de cadena. Uno de los métodos se llama title(). Digamos ->title(true) para convertir todas las palabras en mayúsculas y minúsculas.

... lines 1 - 8
class VinylController
{
... lines 11 - 15
#[Route('/browse/{slug}')]
public function browse(string $slug): Response
{
$title = u(str_replace('-', ' ', $slug))->title(true);
return new Response('Genre: '.$title);
... lines 23 - 24
}
... lines 26 - 27

Ahora, cuando lo probamos... ¡qué bien! ¡Pone las letras en mayúsculas! El componente de la cadena no es especialmente importante, sólo quiero que veas cómo podemos aprovechar partes de Symfony para hacer nuestro trabajo.

Hacer que el comodín sea opcional

Bien: un último reto. Ir a /browse/emo o /browse/death-metal funciona. Pero ir a /browse... no funciona. ¡Está roto! Un comodín puede coincidir con cualquier cosa, pero, por defecto, se requiere un comodín. Tenemos que ir a/browse/<something>.

¿Podemos hacer que el comodín sea opcional? Por supuesto Y es deliciosamente sencillo: haz que el argumento correspondiente sea opcional.

... lines 1 - 8
class VinylController
{
... lines 11 - 15
#[Route('/browse/{slug}')]
public function browse(string $slug = null): Response
{
... lines 20 - 24
}
... lines 26 - 27

En cuanto lo hagamos, le dirá a la capa de enrutamiento de Symfony que no es necesario que el {slug} esté en la URL. Así que ahora cuando refrescamos... funciona. Aunque no es un buen mensaje para la página.

Veamos. Si hay un slug, pon el título como estábamos. Si no, pon$title a "Todos los géneros". Ah, y mueve el "Género:" aquí arriba... para que abajo en el Response podamos pasar simplemente $title.

... lines 1 - 8
class VinylController
{
... lines 11 - 15
#[Route('/browse/{slug}')]
public function browse(string $slug = null): Response
{
if ($slug) {
$title = 'Genre: '.u(str_replace('-', ' ', $slug))->title(true);
} else {
$title = 'All Genres';
}
return new Response($title);
... lines 27 - 28
}
... lines 30 - 31

Inténtalo. En /browse... "Todos los géneros". En /browse/emo... "Género: Emo".

Siguiente: poner un texto como éste en un controlador.... no es muy limpio ni escalable, especialmente si empezamos a incluir HTML. No, tenemos que hacer una plantilla. Para ello, vamos a instalar nuestro primer paquete de terceros y seremos testigos del importantísimo sistema de recetas de Symfony en acción.

Leave a comment!

16
Login or Register to join the conversation
Default user avatar
Default user avatar boban_dj | posted hace 1 año

I have to add: use function Symfony\Component\String\u; to make it work. VSCodium does not show autocomplete, but here is well explained too https://symfony.com/doc/cur...

4 Reply

Hey boban_dj

The fuction import is not shown in the video but if you look on the code blocks from the script, you'll see Ryan used exactly that function. Good job finding it out.

Cheers!

1 Reply
Prim N. Avatar
Prim N. Avatar Prim N. | posted hace 1 año

i really want to say i appreciate these videos ...for someone who is new on symfony your way of doing things makes super sense

1 Reply
Default user avatar
Default user avatar unknown | posted hace 13 días | edited
Comment was deleted.

Hey @Mazzucato ,

Both ways work actually. If you would read the last sentence in that explanation:

And even though {highlight} is in the route, you do not need to have an argument for that!

we mean that you don't have to give every wildcard a corresponding argument in the method signature. You can skip that $highlight at all as in our example. E.g. it may just exist there but you don't need it in the method's code, i.e. don't use it there. That's what we meant there :)

Cheers!

1 Reply
Azundah-Ibuchi Avatar
Azundah-Ibuchi Avatar Azundah-Ibuchi | posted hace 1 mes

i really want to say i appreciate these videos ...for someone who is new on symfony your way of doing things makes super sense sure

Reply

I'm glad to know you like our videos. Cheers!

Reply
Azundah-Ibuchi Avatar
Azundah-Ibuchi Avatar Azundah-Ibuchi | posted hace 1 mes

Good one

Reply
Sebastien D. Avatar
Sebastien D. Avatar Sebastien D. | posted hace 1 año

Hi!
My application has to manage multiple domains. When I'm using the 'host' route parameter, it matches differently if user entered 'www' or not in the browser: www.example.com and example.com are not considered as the same host. How can I add a wildcard that could solve that?
Thanks!

Reply

Hey Sebastien D.

I think a better solution would be to add a redirect at the web server level, for example all requests coming from www.example.com should redirect to example.com, or viceversa.

Cheers!

Reply
Default user avatar
Default user avatar Eskinder | posted hace 1 año | edited

Hi, I am working on Ubuntu 20.04 and installed apache2,php, mysql and composer. On symfony 6 , #[Route('/browse')] doesn't work. I have 404 page "The requested URL was not found on this server." I have installed "symfony/apache-pack". I still have the same result.

`#[Route('/browse')]

public function browse(): Response
{
    return new Response('Browse page');
}`

Best regards

Reply

Hey Eskinder,

First of all, please, make sure the route is exist in the system - for this, run:

$ bin/console debug:router | grep browse

Do you see the route in the output? is it just "/browse" or does it has a prefix? Also, make sure this route is registered for GET (or any) requests. If so, then most probably the problem in your Apache config. First of all, make sure you set the public/ directory as your document root. Then, double-check the other config. Actually, Symfony gives the correct Apache/Nginx configs in the docs, see: https://symfony.com/doc/cur... - there's important to know if you configure it via PHP-FPM or in a different way - there are different configs. But basically, if the route exist and you configured the host well using the config from the docs - it should work. Also, don't forget to restart your Apache server after tweaking any config - that's important, most users forget to do this :)

I hope this helps!

Cheers!

1 Reply

Hi,

I have a strange question, I hope you will understand me.

In your example, "/browse" is hard coded.

Is it possible to save "/ browse" in the database and call from there.

Problem is if user wants to change "/browse" to "/somethig-else" he can't do that from cms it must be changed in code.

Best regards

Reply
Paul R. Avatar
Paul R. Avatar Paul R. | sasa1007 | posted hace 1 año | edited

Yes, that is why you have the {wildcard} route. So all you need to do is change the /browse route to /{browse} now it matches anything you pass to it and you can check if that route or slug exists in your database

Reply

So /{browse} cen be value from data base?

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/asset": "6.0.*", // v6.0.3
        "symfony/console": "6.0.*", // v6.0.3
        "symfony/dotenv": "6.0.*", // v6.0.3
        "symfony/flex": "^2", // v2.1.5
        "symfony/framework-bundle": "6.0.*", // v6.0.4
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.3
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-turbo": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/twig": "^2.12|^3.0" // v3.3.8
    },
    "require-dev": {
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/stopwatch": "6.0.*", // v6.0.3
        "symfony/web-profiler-bundle": "6.0.*" // v6.0.3
    }
}
userVoice