Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Convertidor de parámetros y 404

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Hemos programado el camino feliz. Cuando voy a /mix/13, mi base de datos sí encuentra una mezcla con ese id y... la vida es buena. Pero, ¿y si lo cambio a /99? Vaya, eso es un error 500: no es algo que queramos que nuestro sitio haga nunca. En realidad debería ser un error 404. Entonces, ¿cómo activamos un 404?

Activar una página 404

En el método, esta variable $mix será un objeto VinylMix o null si no se encuentra ninguno. Así que podemos decir if (!$mix), y luego, para desencadenar un 404,throw $this->createNotFoundException(). Puedes darle un mensaje si quieres, pero sólo lo verán los desarrolladores.

... lines 1 - 11
class MixController extends AbstractController
{
... lines 14 - 35
public function show($id, VinylMixRepository $mixRepository): Response
{
... lines 38 - 39
if (!$mix) {
throw $this->createNotFoundException('Mix not found');
}
... lines 43 - 46
}
}

Este createNotFoundException(), como su nombre indica, crea un objeto de excepción. Así que en realidad estamos lanzando una excepción aquí... lo que está bien, porque significa que el código que sigue a esto no se ejecutará.

Ahora bien, normalmente si tú o algo de tu código lanza una excepción, provocará un error 500. Pero este método crea un tipo especial de excepción que se corresponde con un 404. ¡Observa! Aquí, en la parte superior derecha, cuando refresco... ¡404!

Por cierto, este no es el aspecto que tendrían las páginas 404 o 500 en producción. Si pasáramos al entorno prod, veríamos una página de error bastante genérica y sin detalles. Luego puedes personalizar su aspecto, incluso haciendo estilos separados para los errores 404, 403 Acceso Denegado, o incluso... gasp... 500 errores si algo va realmente mal. Consulta la documentación de Symfony para saber cómo personalizar las páginas de error.

Convertidor de parámetros: Consulta automática

¡Muy bien! Hemos consultado un único objeto VinylMix e incluso hemos gestionado la ruta 404. Pero podemos hacerlo con mucho menos trabajo. ¡Compruébalo! Sustituye el argumento $id por un nuevo argumento, de tipo con nuestra clase de entidad VinylMix. Llámalo, qué tal, $mix para que coincida con la variable de abajo. Luego... elimina la consulta... y también el 404. Y ahora, ni siquiera necesitamos el argumento $mixRepository.

... lines 1 - 35
public function show(VinylMix $mix): Response
{
return $this->render('mix/show.html.twig', [
'mix' => $mix,
]);
}
... lines 42 - 43

Esto... merece alguna explicación. Hasta ahora, las "cosas" que se nos "permiten" como argumentos de nuestros controladores son (1) comodines de ruta como $id o (2) servicios. Ahora tenemos una tercera cosa. Cuando escribes una clase de entidad, Symfony consultará el objeto automáticamente. Como tenemos un comodín llamado {id}, tomará este valor (por ejemplo "99" o "16") y buscará un VinylMix cuyo idsea igual a ese. El nombre del comodín - id en este caso - debe coincidir con el nombre de la propiedad que debe utilizar para la consulta.

Pero si vuelvo y actualizo... ¡no funciona!

No se puede autoconducir el argumento $mix de MixController::show(): hace referencia a VinylMix pero no existe tal servicio.

Sabemos que no es un servicio... así que tiene sentido. Pero... ¿por qué no consulta el objeto como acabo de decir?

Porque... para que esta función funcione, ¡tenemos que instalar otro bundle! Bueno, si estás usando Symfony 6.2 y un DoctrineBundle suficientemente nuevo - probablemente la versión 2.8 - entonces esto debería funcionar sin necesidad de nada más. Pero como estamos usando Symfony 6.1, necesitamos una librería extra.

Busca tu terminal y di:

composer require sensio/framework-extra-bundle

Este es un bundle lleno de pequeños y bonitos atajos que, para Symfony 6.2, se habrán trasladado al propio Symfony. Así que, con el tiempo, no necesitarás esto.

Y ahora... sin hacer nada más... ¡funciona! ¡Se ha consultado automáticamente el objeto VinylMix y la página se renderiza! Y si vas a un ID malo, como/99... ¡sí! ¡Compruébalo! ¡Obtenemos un 404! Esta función se llama "ParamConverter"... que se menciona en el error:

Objeto VinylMix no encontrado por la anotación @ParamConverter.

En cualquier caso, me encanta esta función. Si necesito consultar varios objetos, como en la acción browse(), utilizaré el servicio de repositorio correcto. Pero si necesito consultar un solo objeto en un controlador, utilizo este truco.

A continuación, vamos a hacer posible que nuestras mezclas sean votadas por arriba y por abajo aprovechando un simple formulario. Para ello, por primera vez, actualizaremos una entidad en la base de datos.

Leave a comment!

10
Login or Register to join the conversation
Default user avatar
Default user avatar unknown | posted hace 1 mes | edited
Comment was deleted.
t5810 Avatar
t5810 Avatar t5810 | posted hace 4 meses | edited

Hi

I had downloaded the code from this course (folder start), and I am trying to code along. When I attempt to run the command "composer require sensio/framework-extra-bundle ", I get the following error:

In App_KernelDevDebugContainer.php line 772:
!!
!! Attempted to call an undefined method named "registerLoader" of class "Doctrine\Common\Annotations\AnnotationRegistry".

To continue coding while I get answer to this question, i did the following:

  1. I run the command: "composer remove sensio/framework-extra-bundle"
  2. revert the function show as it was.

Any advice how to proceed?

Thanks!

Reply

Hey @t5810!

Ah, sorry for the trouble and the my glacially slow reply! Ok, so I see the issue - there was a bug between older version of Symfony and doctrine/annotations v2 (which is required by the sensio/framework-extra-bundle library). Anyways, the fix is to update one Symfony package:

composer up symfony/framework-bundle

That should fix the issue :). I'm running that on our course code right now so that the downloaded code works for everyone again without issues.

Cheers!

3 Reply
Ted Avatar

Hello, I had the same problem and when I ran composer up symfony/framework-bundle I have this new error :

Attempted to load class "Locale" from the global namespace.
Did you forget a "use" statement?

Reply
Ted Avatar

this is where it says the error is :

C:\Users\Fourt\Desktop\sites\mixedVinyl\vendor\symfony\translation\LocaleSwitcher.php:37

 `        $this->defaultLocale = $locale;    }    public function setLocale(string $locale): void    {        \Locale::setDefault($this->locale = $locale);        $this->requestContext?->setParameter('_locale', $locale);        foreach ($this->localeAwareServices as $service) {            $service->setLocale($locale);        } `
Reply

Hey @Ted

I believe you need to install the PHP intl extension. If you're in a Linux alike environment, you can install by running these commands
sudo apt-get update -y
sudo apt-get install -y php-intl

Cheers!

Reply
Michael-S Avatar
Michael-S Avatar Michael-S | posted hace 5 meses

Let's say there are multiple Users that have their own Mixes, and you can only view other Users' Mixes if you have some kind of active relationship with said User, say if you are "subscribed" to them.

How would you protect against someone viewing a Mix that they're not supposed to? Would you still use the ParamConverter and check that the logged in User is subscribed to the User that owns the Mix that is passed in, or would you not use the ParamConverter and instead query based on the combo of the Mix ID and logged-in User ID, going through some bridge table that keeps track of subscriptions between Users?

Then you either display the Mix if found (knowing at that point the logged-in User has access to it), or NULL would be returned from the query either if the Mix doesn't exist, or the logged-in User has no active subscription, at which point you'd 404.

Or would you approach this case some other way entirely?

Thanks ahead of time.

Reply

Hey Michael,

It depends, but as you see there are many good options, and mostly it's the matter of taste. Sometimes you can prefer some option more just because the solution is easier with it. So, just use that one you would like the most. If in your code, it's easier to check it from the entity that returned by the param converter - great, that's a good way I think. But if it's easier for you to write a custom query and this way looks cleaner for you - great, go for it :) No matter what way you choose, Symfony is flexible enough and allows you to throw 404 error yourself, so both ways are valid technically, depending on which one you like the most, or which one looks cleaner to you, to your specific code.

P.S. usually calling a repository method directly is more obvious, flexible, and straightforward than param converter that mostly used for a very simple fetch. But if you can write a nice code with param converter and throw a custom 404 or even 403 - I don't see any strong arguments against it ;)

Cheers!

Reply
MaxiCom Avatar

Hello,

I was wondering, why on the param converter documentation and on this video, you refers to it as @ParamConverter?
Why is the need for the @, is it because the param converters are services?

Thanks for your time!

Reply

Hey MaxiCom,

The @ syntax in the comments is called PHP annotations, they were pretty popular before the new PHP 8 attributes that starts with # like #[Route('/mix/new')]. So, it's a special syntax that is read by the Symfony system. And yeah, almost everything in Symfony are services ;) but basically those @ParamConverter is just an alternative way of configuration :)

Cheers!

1 Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.7", // v3.7.0
        "doctrine/doctrine-bundle": "^2.7", // 2.7.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.12", // 2.12.3
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.2", // v6.2.6
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
        "symfony/asset": "6.1.*", // v6.1.0
        "symfony/console": "6.1.*", // v6.1.2
        "symfony/dotenv": "6.1.*", // v6.1.0
        "symfony/flex": "^2", // v2.2.2
        "symfony/framework-bundle": "6.1.*", // v6.1.2
        "symfony/http-client": "6.1.*", // v6.1.2
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
        "symfony/runtime": "6.1.*", // v6.1.1
        "symfony/twig-bundle": "6.1.*", // v6.1.1
        "symfony/ux-turbo": "^2.0", // v2.3.0
        "symfony/webpack-encore-bundle": "^1.13", // v1.15.1
        "symfony/yaml": "6.1.*", // v6.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.1.*", // v6.1.0
        "symfony/maker-bundle": "^1.41", // v1.44.0
        "symfony/stopwatch": "6.1.*", // v6.1.0
        "symfony/web-profiler-bundle": "6.1.*", // v6.1.2
        "zenstruck/foundry": "^1.21" // v1.21.0
    }
}
userVoice