gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
En un futuro tutorial, vamos a crear una base de datos para gestionar las canciones, los géneros y los discos de vinilo mezclados que nuestros usuarios están creando. Ahora mismo, estamos trabajando completamente con datos codificados... pero nuestros controladores -y- especialmente las plantillas no serán muy diferentes una vez que hagamos todo esto dinámico.
Así que este es nuestro nuevo objetivo: quiero crear una ruta de la API que devuelva los datos de una sola canción como JSON. Vamos a usar esto en unos minutos para dar vida a este botón de reproducción. Por el momento, ninguno de estos botones hace nada, pero tienen un aspecto bonito.
Crear el controlador JSON
Los dos pasos para crear un punto final de la API son... exactamente los mismos que para crear una página HTML: necesitamos una ruta y un controlador. Como esta ruta de la API devolverá datos de canciones, en lugar de añadir otro método dentro de VinylController
, vamos a crear una clase de controlador totalmente nueva. La forma en que organices este material depende enteramente de ti.
Crea una nueva clase PHP llamada SongController
... o SongApiController
también sería un buen nombre. En su interior, ésta comenzará como cualquier otro controlador, extendiendoAbstractController
. Recuerda: esto es opcional... pero nos proporciona métodos de acceso directo sin inconvenientes.
A continuación, crea un public function
llamado, qué tal, getSong()
. Añade la ruta... y pulsa el tabulador para autocompletar esto de forma que PhpStorm añada la declaración de uso en la parte superior. Establece la URL como /api/songs/{id}
, donde id
será finalmente el id de la base de datos de la canción.
Y como tenemos un comodín en la ruta, se nos permite tener un argumento $id
. Por último, aunque no necesitamos hacerlo, como sabemos que nuestro controlador devolverá un objeto Response
, podemos establecerlo como tipo de retorno. Asegúrate de autocompletar el del componente HttpFoundation
de Symfony.
Dentro del método, para empezar, dd($id)
... sólo para ver si todo funciona.
namespace App\Controller; | |
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\Routing\Annotation\Route; | |
class SongController extends AbstractController | |
{ | |
'/api/songs/{id}') ( | |
public function getSong($id): Response | |
{ | |
dd($id); | |
} | |
} |
¡Vamos a hacerlo! Dirígete a /api/songs/5
y... ¡lo tienes! Otra página nueva.
De vuelta a ese controlador, voy a pegar algunos datos de la canción: finalmente, esto vendrá de la base de datos. Puedes copiarlo del bloque de código de esta página. Nuestro trabajo es devolverlo como JSON.
Entonces, ¿cómo devolvemos JSON en Symfony? Devolviendo un nuevo JsonResponse
y pasándole los datos.
... lines 3 - 5 | |
use Symfony\Component\HttpFoundation\JsonResponse; | |
... lines 7 - 9 | |
class SongController extends AbstractController | |
{ | |
'/api/songs/{id}') ( | |
public function getSong($id): Response | |
{ | |
// TODO query the database | |
$song = [ | |
'id' => $id, | |
'name' => 'Waterfalls', | |
'url' => 'https://symfonycasts.s3.amazonaws.com/sample.mp3', | |
]; | |
return new JsonResponse($song); | |
} | |
} |
Lo sé... ¡demasiado fácil! Refresca y... ¡hola JSON! Ahora puedes estar pensando:
¡Ryan! Nos has estado diciendo -repetidamente- que un controlador debe devolver siempre un objeto Symfony
Response
, que es lo que devuelverender()
. ¿Ahora devuelve otro tipo de objetoResponse
?
Vale, es justo... pero esto funciona porque JsonResponse
es una Respuesta. Me explico: a veces es útil saltar a las clases principales para ver cómo funcionan. Para ello, en PHPStorm -si estás en un Mac mantén pulsado comando, si no, mantén pulsado control- y luego haz clic en el nombre de la clase a la que quieras saltar. Y... ¡sorpresa! JsonResponse
extiende Response
. Sí, seguimos devolviendo un Response
. Pero esta subclase está bien porque codifica automáticamente JSON nuestros datos y establece la cabeceraContent-Type
en application/json
.
Ah, y de vuelta a nuestro controlador, podemos ser aún más perezosos diciendoreturn $this->json($song)
... donde json()
es otro método abreviado que viene de AbstractController
.
... lines 3 - 9 | |
class SongController extends AbstractController | |
{ | |
'/api/songs/{id}') ( | |
public function getSong($id): Response | |
{ | |
... lines 15 - 20 | |
return $this->json($song); | |
} | |
} |
Hacer esto no supone ninguna diferencia, porque sólo es un atajo para devolver ... ¡un JsonResponse
!
Si estás construyendo una API seria, Symfony tiene un componenteserializer
que es realmente bueno para convertir objetos en JSON... y luego JSON de nuevo en objetos. Hablamos mucho de él en nuestro tutorial de la Plataforma API, que es una potente biblioteca para crear APIs en Symfony.
A continuación, vamos a aprender cómo hacer que nuestras rutas sean más inteligentes, por ejemplo, haciendo que un comodín sólo coincida con un número, en lugar de coincidir con cualquier cosa.
I am getting the Json response like this:
{"id":"5","name":"Waterfalls","url":"https:\/\/symfonycasts.s3.amazonaws.com\/sample.mp3"}
Even when I copy and paste entire controller, it doesn't change
Hey @Artun!
Actually, that looks perfect to me! It looks a bit different than what you see in the video because I have a browser plugin that makes it look fancy (my bad - I should have mentioned that). In JSON, having quotes around the keys = like "id"
is correct. In the video, that browser plugin is hiding those quotes to make it look prettier. But your response is exactly what we want :).
Cheers!
Hey @weaverryan!
Thanks for your reply.So I am assuming the backslaches in the url is also expected? instead of "https://symfonycasts.s3.amazonaws.com/sample.mp3"
it shows as
"https:\/\/symfonycasts.s3.amazonaws.com\/sample.mp3"
Yes @Artun, the "JSON encode" function takes care of escaping any reserved character, for example, if you have double quotes in your data, those will be escaped too
Cheers!
This lesson did not work for me as instructed. I needed to add the following in order for it to work.
use Symfony\Component\HttpFoundation\JsonResponse;
good Tutorials, I come from Laravel and everything make sense its more lighter then laravel or lumen but when I saw you how you created Controller file I thought there should be easier way something like command "php artisan make:controller" to do, what I found there is "php bin/console make:crontroller" it requires "maker" you should show this too it makes your life easier
Hey Engazan,
Actually, there's an easier way for controller, you need to have Maker bundle installed and then as you said you can call:
bin/console make:controller
To be able to use the Maker bundle (if you don't have one) - first, you need to execute:
composer require maker
We wanted to show the strightforawrd way focusing the attention on no magic, you just need to create a simple PHP class in the specific folder. We will show the way with Maker in future tutorials too. ;)
Cheers!
Hi again, Ryan.
I just ran into another issue, and this time I made sure that the code in my controller doesn't miss anything.
It looks like my Symfony is somehow ignoring the fact that I created a brand-new route, because upon trying to open it in the browser, the framework is throwing a 404 Exception: No route found for "GET https://127.0.0.1:8000/api/songs/5"
.
Moreover, by issuing the bin/console debug:router
command, the new route isn't showing up. Clearing up the caches and restarting the server didn't make any difference either.
Here's my SongController
code:
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SongController extends AbstractController
{
#[Route('/api/songs/{id}')]
public function getSong($id): Response
{
dd($id);
}
What's happening? Any ideas?
Thank you so much.
Hey @roenfeldt!
Hmm.
Moreover, by issuing the bin/console debug:router command, the new route isn't showing up
That was a great thing to try for debugging. And while you shouldn't need to clear the cache ever, when you hit a super weird issue it's always a good thing to try as well. So nice job there.
So, why isn't this working? First, remove that line break between the #[Route]
and the public function
. I don't think that's the issue (I had never tried it before, but having a line break doesn't seem to cause any issues) - but better to remove it. To other things to check, which will sound crazy, but there is very little else that could be going wrong: (A) make sure you have the <?php
at the start of your file (I have seen this many times and it's super hard to debug) and (B) triple-check that you're inside of the src/Controller/
directory.
The mechanism for loading these attribute routes is pretty simple: in your config/routes.yaml
directory, there is a line there that says
look at all php files in
src/Controller
and parse their#[Route]
attributes.
It's these 3 lines - https://github.com/symfony/recipes/blob/main/symfony/routing/6.1/config/routes.yaml#L1-L3 . So, there's not a lot that can go wrong as long as you have a valid php file in the src/Controller/
directory (and the contents of your class - apart from the potential missing <?php
look perfect).
Let me know if any of this helps :).
Cheers!
Hi Ryan,
First off, thank you for the fast reply, and thank you for the very nice explanation!
With the help of your suggestions, I realized that PhpStorm created the file into the wrong folder. My SongController
was created inside src/App/Controller
. How did that happen? Well, the error was, once again, on the exterior of the screen, of course :)
Because I learned my lesson from the previous time (the Controller's namespace was missing the App\
part), in order to prevent that from happening again, I used PhpStorm's New > PHP Class
dialog (just like you did in the video), but as an extra step, I updated the Namespace field by prepending App\
to the existing Controller
value. And then I pressed Return, of course without realizing that PhpStorm will create the wrongful folder structure based on what I chose in there.
If I didn't do that, the path for the new Controller would have been correct, but then the namespace inside it would have caused Symfony to throw the Controller (...) has no container set, did you forget to define it as a service subscriber?
500 Exception.
To conclude, your suggestions put me back on track, for which I am grateful. But my question now is, how would you suggest I'd handle this problem, because I need PhpStorm to automatically prepend App\
to the namespace of any new Controller I'd be creating, just like it does in your video?
Thank you so much, Ryan! :)
Hey @roenfeldt!
Yea, the "New -> PHP Class" dialog is great - and it is definitely NOT working correctly for you. In order for that to work, PhpStorm needs to know which namespace lives in which directory. In a Symfony app, the App\
namespace lives in src
. This convention isn't hardcoded into Symfony - it's actually a setting in your composer.json
file: https://github.com/symfony/skeleton/blob/948939fb8ac6b1ab38547bff6f386bca3a782ed4/composer.json#L35-L39
For the last 2-ish years, PHPStorm should automatically read this setting and use that when you create classes. For some reason that's not happening, or it's reading it incorrectly. Or, that feature just isn't activated for some reason. Go into your PHPStorm settings. Inside, go to PHP -> Composer
and look for a check box that says Synchronize IDE Settings with composer.json
- I believe this needs to be activated.
Let me know if that helps :).
Cheers!
Hey Ryan,
Enabling the Synchronize IDE Settings with composer.json
checkbox did the trick. Yay! :D
Thank you for clarifying this out for me!
Cheers! :)
// 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
}
}
Decided to have a look at the example url for the JSON endpoint...
I'm not even mad