Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Rutas Inteligentes: Solo POST y Validación de {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.

Dentro de nuestro JavaScript, estamos haciendo una petición POST a la API. Y tiene sentido. El tema de "cual método HTTP" - como GET, POST, PUT, etc - se supone debes usar para un llamado a la API... puede ser complicado. Pero como nuestra ruta eventualmente va a cambiar algo en la base de datos, como práctica recomendable, no queremos permitir a la gente que hagan llamados tipo GET a nuestra ruta. Por ahora, podemos hacer un llamado GET con tan solo poner la URL en nuestro navegador. Hey! Acabo de votar!

Para mejorar esto, en el CommentController, podemos hacer más inteligente a nuestra ruta, podemos hacer que solo funcione cuando el método sea POST. Para lograrlo agrega methods="POST".

... lines 1 - 8
class CommentController extends AbstractController
{
/**
* @Route("/comments/{id}/vote/{direction}", methods="POST")
*/
public function commentVote($id, $direction)
{
... lines 16 - 25
}
}

Tan pronto lo hagamos, al refrescar... error 404! La ruta ya no se encuentra

Tip

De hecho, es un error 405! Método HTTP no permitido.

El Comando router:match

Otra buena forma de ver esto es en tu terminal. Corre: php bin/console router:match. Luego copia la URL... y pegala.

php bin/console router:match /comments/10/vote/up

Este divertido comando nos dice cuál ruta le pertenece a una URL. En este caso, ninguna ruta fue encontrada pero esto nos dice que casi encuentra la ruta app_comment_commentvote.

Para ver si un llamado POST sería encontrado, pasa --method=POST:

php bin/console router:match /comments/10/vote/up --method=POST

Y... Bum! Nos muestra la ruta que pudo encontrar y todos los detalles, incluyendo el controlador.

Restringiendo un {Comodín}

Pero hay algo más que no está del todo bien con nuestra ruta. La ruta espera que la parte {direction} sea arriba o abajo. Pero... técnicamente, alguien podría poner plátano en la URL. De hecho, probémoslo: Cambia la dirección por plátano:

php bin/console router:match /comments/10/vote/banana --method=POST

Si! Votamos "plátano" para este comentario! No es el fin del mundo... si un usuario intenta hackear nuestro sistema y hace esto, solo significaría un voto negativo. Pero podemos hacerlo mejor.

Como has de saber, normalmente un comodín se empareja con cualquier cosa. Sin embargo, si quisieras, puedes controlarlo con una expresión regular. Dentro de {}, pero después del nombre, agrega <>. Dentro, escribe up|down.

... lines 1 - 8
class CommentController extends AbstractController
{
/**
* @Route("/comments/{id}/vote/{direction<up|down>}", methods="POST")
*/
public function commentVote($id, $direction)
{
... lines 16 - 25
}
}

Ahora prueba el comando router:match

php bin/console router:match /comments/10/vote/banana --method=POST

Si! No encuentra la ruta porque plátano no es arriba o abajo. Si cambiamos esto por arriba, funciona:

php bin/console router:match /comments/10/vote/up --method=POST

Como Hacer que el id Solo Funcione con Enteros?

Por cierto, podrías ser tentado a hacer más inteligente el comodín {id}. Asumiendo que usamos ids con auto incremento en la base de datos, sabemos que el id debe de ser un entero. Para hacer que esta ruta solo funcione si la parte del id es un número, puedes agregar <\d+>, lo que significa: encuentra un "dígito" con cualquier tamaño.

... lines 1 - 8
class CommentController extends AbstractController
{
/**
* @Route("/comments/{id<\d+>}/vote/{direction<up|down>}", methods="POST")
*/
public function commentVote($id, $direction)
{
... lines 16 - 25
}
}

Pero... En realidad no voy a poner esto aquí. Por qué? Eventualmente vamos a usar $id para llamar a la base de datos. Si alguien escribe plátano aquí, a quien le importa? El query no va a encontrar ningún comentario con plátano como id y vamos a agregar algo de código para retornar una página 404. Incluso si alguien intenta hacer un ataque de inyección de SQL, como aprenderás más tarde en nuestro tutorial de base de datos, no habría problema, porque la capa de la base de datos nos protege de ello.

... lines 1 - 8
class CommentController extends AbstractController
{
/**
* @Route("/comments/{id}/vote/{direction<up|down>}", methods="POST")
*/
public function commentVote($id, $direction)
{
... lines 16 - 25
}
}

Hay que asegurarnos que todo aún funciona. Voy a cerrar una pestaña del navegador y refrescar la página. Eso! los votos aún se ven bien.

A continuación, demos un vistazo a la parte más fundamental de Symfony: Los servicios.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}