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

Rutas JSON en la API

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.

Una de las funcionalidades en nuestro sitio... la cual aun no funciona... es la de votar a favor o en contra en las respuestas de las preguntas. Eventualmente, cuando hagas click arriba o abajo, esto hará un request tipo AJAX a una ruta de una API que vamos a crear. Esa ruta va a guardar el voto en la base de datos y va a responder con un JSON que contendrá el nuevo total de votos y así nuestro JavaScript podrá actualizar el contador.

Aún no tenemos una base de datos en nuestra aplicación, pero estamos listos para construir todas las otras partes de esta funcionalidad.

Creando una Ruta JSON

Comencemos por crear una ruta tipo JSON para la API a la cual accederemos con AJAX cuando el usuario vote en una respuesta arriba o abajo.

Podríamos crear esto en QuestionController como un nuevo método. Pero como esta ruta en realidad trabaja con un "comment", vamos a crear un nuevo controlador. Llámalo CommentController.

Como la vez pasada, vamos a escribir extends AbstractController y presionar tab para que PhpStorm autocomplete esto y agregue el import en la parte de arriba. Al extender de esta clase nos brinda métodos de atajo... y me encantan los atajos!

... lines 1 - 2
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class CommentController extends AbstractController
{
}

Dentro, crea una función pública. Puede tener cualquier nombre... que tal commentVote(). Agrega la ruta arriba: /**, luego @Route. Autocompleta la del componente Routing para que asi PhpStorm agregue el import.

Para la URL, que tal /comments/{id} - esto eventualmente será el id del comentario específico en la base de datos - /vote/{direction}, donde {direction} será cualquiera de las palabras arriba o abajo.

y como tenemos estos dos comodines, podemos agregar dos argumentos: $id y $direction. Empezaré con un comentario: el $id será súper importante después cuando tengamos una base de datos... pero no lo vamos a usar por ahora.

... lines 1 - 6
use Symfony\Component\Routing\Annotation\Route;
... line 8
class CommentController extends AbstractController
{
/**
* @Route("/comments/{id}/vote/{direction}")
*/
public function commentVote($id, $direction)
{
... lines 16 - 25
}
}

Sin una base de datos, vamos a simular la lógica. Si $direction == 'up', entonces normalmente guardaríamos el voto a favor en la base de datos y consultaríamos el nuevo total de votos. En vez de eso, escribe $currentVoteCount = rand(7, 100).

... lines 1 - 8
class CommentController extends AbstractController
{
... lines 11 - 13
public function commentVote($id, $direction)
{
// todo - use id to query the database
// use real logic here to save this to the database
if ($direction === 'up') {
$currentVoteCount = rand(7, 100);
} else {
... line 22
}
... lines 24 - 25
}
}

El conteo de votos está escrito directamente en el template con un total de 6. Así que esto hará que el nuevo conteo de votos parezca ser un nuevo número mayor que este. En el else, haz lo opuesto: un número aleatorio entre 0 y 5.

... lines 1 - 8
class CommentController extends AbstractController
{
... lines 11 - 13
public function commentVote($id, $direction)
{
// todo - use id to query the database
// use real logic here to save this to the database
if ($direction === 'up') {
$currentVoteCount = rand(7, 100);
} else {
$currentVoteCount = rand(0, 5);
}
... lines 24 - 25
}
}

Si, esto será mucho más interesante cuando tengamos una base de datos, pero va a funcionar muy bien para nuestro propósito.

Regresando un JSON?

La pregunta ahora es: después de "Guardar" el voto en la base de datos, que debería de retornar el controlador? Bueno, probablemente debería retornar un JSON... y que quiero incluir el nuevo conteo en la respuesta para que nuestro JavaScript pueda utilizarlo y actualizar el número de votos.

Así que... como regresamos un JSON? Recuerda: nuestro único trabajo en un controlador es retornar un objeto de tipo Symfony Response. JSON no es nada más que una respuesta cuyo contenido es una cadena JSON en vez de HTML. Así que podemos poner: return new Response() con json_encode() con algún dato.

Pero! en vez de eso, return new JsonResponse() - autocompleta esto para que PhPStorm agregue el import. Pasa un array con los datos que queremos. Que tal pasar la llave votes con $currentVoteCount.

... lines 1 - 5
use Symfony\Component\HttpFoundation\JsonResponse;
... lines 7 - 8
class CommentController extends AbstractController
{
... lines 11 - 13
public function commentVote($id, $direction)
{
// todo - use id to query the database
// use real logic here to save this to the database
if ($direction === 'up') {
$currentVoteCount = rand(7, 100);
} else {
$currentVoteCount = rand(0, 5);
}
return new JsonResponse(['votes' => $currentVoteCount]);
}
}

Ahora... tal vez estés pensando:

Ryan! Te la pasas diciendo que debemos retornar un objeto Response... y acabas de retornar algo diferente. Esto es una locura!

Es un punto válido. Pero! si presionas Command or Ctrl y das click en la clase JsonResponse, vas a aprender que JsonResponse hereda de Response. Esta clase no es nada más que un atajo para crear respuestas tipo JSON: esto hace el JSON encode a los datos que le pasamos y se asegura que la cabecera Content-Type sea asignada a application/json, la cual ayuda a las librerías AJAX a entender que estamos regresando un JSON.

Así que... ah! Probemos nuestra nueva y brillante ruta de la API! Copia la URL, abre un nueva nueva pestaña en el navegador, pega y llena los comodines: que tal 10 para el {id} y para el voto "up". Presiona enter. Hola ruta JSON!

El punto clave más importante aquí es: las respuestas JSON no son nada especiales.

El Método atajo json()

La clase JsonResponse nos hace la vida más sencilla.. pero podemos ser aún más flojos! En vez de new JsonResponse simplemente escribe return $this->json().

... lines 1 - 8
class CommentController extends AbstractController
{
... lines 11 - 13
public function commentVote($id, $direction)
{
... lines 16 - 24
return $this->json(['votes' => $currentVoteCount]);
}
}

Esto no cambia nada: es solo un atajo para crear el mismo objeto JsonResponse. Pan comido.

El Serializador de Symfony

Por cierto, uno de los "componentes" de Symfony se llama "Serializer", y es muy bueno convirtiendo objetos a JSON o XML. Aún no lo hemos instalado, pero si lo hiciéramos, el $this->json() empezaría a utilizarlo para serializar cualquier cosa que le pasemos. No haría ninguna diferencia en nuestro caso donde pasamos un array, pero significa que podrías empezar a pasar objetos a $this->json(). Si quieres saber más - o quieres construir una muy sofisticada API - ve nuestro tutorial sobre API Platform: un increíble bundle de Symfony para construir APIs.

A continuación, escribamos algo de JavaScript que hará un llamado AJAX a nuestra nueva ruta. También vamos a aprender como agregar JavaScript global y JavaScript específico a una página

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
    }
}