Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Pasar valores a Stimulus

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.

Establecer una variable global está bien. Pero si utilizas Stimulus, hay una forma mejor. Podemos pasar datos del servidor como un valor a un controlador Stimulus.

Por supuesto, esto es una aplicación Vue. Pero si miras en templates/main/homepage.html.twig, estamos utilizando el paquete symfony/ux-vue para renderizar esto:

... lines 1 - 2
{% block body %}
<div {{ vue_component('TreasureConnectApp', {
entrypoint: path('api_entrypoint')
}) }}></div>
{% endblock %}

Entre bastidores, eso activa un pequeño controlador Stimulus que inicia y renderiza el componente Vue. Cualquier argumento que pasemos aquí se envía al controlador Stimulus como un valor... y luego se reenvía como props a la aplicación Vue. Lo que vamos a hacer es "más o menos" específico de Vue, pero podrías utilizar esta estrategia para pasar valores a cualquier controlador Stimulus.

Primero, en el componente Vue, vamos a permitir que se pase una nueva prop llamada user:

... lines 1 - 26
<script setup>
... lines 28 - 32
const props = defineProps(['entrypoint', 'user'])
... lines 34 - 40
</script>

Si no utilizas Vue, no te preocupes demasiado por los detalles. Para asegurarnos de que llega aquí console.log(props.user). E inicializa los datos enprops.user:

... lines 1 - 26
<script setup>
... lines 28 - 32
const props = defineProps(['entrypoint', 'user'])
console.log(props.user);
const user = ref(props.user);
... lines 36 - 40
</script>

A continuación, en base.html.twig, quita todo eso de window.user:

<!DOCTYPE html>
<html>
... lines 3 - 15
<body>
{% block body %}{% endblock %}
</body>
</html>

Y en homepage.html.twig, pasa un nuevo user prop set a app.user:

... lines 1 - 2
{% block body %}
<div {{ vue_component('TreasureConnectApp', {
entrypoint: path('api_entrypoint'),
user: app.user
}) }}></div>
{% endblock %}

Ahora, si te mueves y actualizas, ¿no funciona? Parece que estamos autenticados como... ¿nada?

Serializar antes de pasar el valor

Si escarbas un poco, verás que estamos enviando el user a Stimulus como {} vacío. ¿Por qué? Porque cuando envías datos a Stimulus, éste no utiliza el serializador para transformarlos en JSON: sólo utiliza json_encode(). Y eso no es suficiente.

Así que tenemos que serializarlo nosotros mismos. Para ello, abresrc/Controller/MainController.php. Aquí está el controlador que renderiza esa plantilla. Autoconecta un servicio llamado NormalizerInterface y luego pasa una variable a nuestra plantilla llamada userData ajustada a $normalizer->normalize(). Oh, ¡pero necesitamos al usuario! Añade otro argumento al controlador con el nuevo atributo#[CurrentUser], type-hint User, digamos $user, y luego = null en caso de que no estemos autenticados. Más abajo, la normalización convertirá el objeto en una matriz. Así que pasa $user y luego el formato de la matriz, que es jsonld: queremos todos los campos JSON-LD. Por último, pasa el contexto de serialización con'groups' => 'user:read':

... lines 1 - 4
use App\Entity\User;
... lines 6 - 8
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class MainController extends AbstractController
{
#[Route('/')]
public function homepage(NormalizerInterface $normalizer, #[CurrentUser] User $user = null): Response
{
return $this->render('main/homepage.html.twig', [
'userData' => $normalizer->normalize($user, 'jsonld', [
'groups' => ['user:read'],
]),
]);
}
}

¡Último paso! En la plantilla, establece la propiedad user en userData:

... lines 1 - 2
{% block body %}
<div {{ vue_component('TreasureConnectApp', {
... line 5
user: userData,
}) }}></div>
{% endblock %}

Ya que el sistema Stimulus ejecutará ese array a través de json_encode() que transformará ese array en JSON. Cuando pasemos y refresquemos .... ¡ya lo tienes! Puedes ver que todo el JSON se pasa al controlador Stimulus... y luego se pasa a Vue como prop.

Vuelve a girar y asegúrate de sacar ese console.log() de ahí:

... lines 1 - 26
<script setup>
... lines 28 - 33
console.log(props.user);
... lines 35 - 40
</script>

Protección CSRF

Todavía no lo hemos visto, pero cuando empecemos a hacer peticiones a nuestra API, esas peticiones se autenticarán gracias a la sesión. Cuando utilices sesiones con tu API, puede que leas que necesitas protección CSRF. ¿Necesitamos tokens CSRF?

La respuesta rápida es: probablemente no. Mientras utilices algo llamado cookies SameSite - que son automáticas en Symfony - entonces tu API probablemente no necesite preocuparse por la protección CSRF. Pero ten en cuenta dos cosas. En primer lugar, asegúrate de que tus peticiones GET no tienen efectos secundarios. No hagas una tontería como permitir que el cliente de la API haga una petición GET... pero luego guardes algo en la base de datos. En segundo lugar, algunos navegadores antiguos -como IE 11- no admiten las cookies SameSite. Así que al renunciar a los tokens CSRF, podrías estar permitiendo que un pequeño porcentaje de tus usuarios sean susceptibles de sufrir ataques CSRF.

Si quieres saber más, nuestro tutorial sobre la API Platform 2 tiene un capítulo entero sobre Cookies SameSite y tokens CSRF.

A continuación, pasemos al otro caso de uso de la autenticación: Los tokens API.

Leave a comment!

0
Login or Register to join the conversation
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": "*",
        "api-platform/core": "^3.0", // v3.1.2
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.8.3
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.1
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.66.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.16.1
        "symfony/asset": "6.2.*", // v6.2.5
        "symfony/console": "6.2.*", // v6.2.5
        "symfony/dotenv": "6.2.*", // v6.2.5
        "symfony/expression-language": "6.2.*", // v6.2.5
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.5
        "symfony/property-access": "6.2.*", // v6.2.5
        "symfony/property-info": "6.2.*", // v6.2.5
        "symfony/runtime": "6.2.*", // v6.2.5
        "symfony/security-bundle": "6.2.*", // v6.2.6
        "symfony/serializer": "6.2.*", // v6.2.5
        "symfony/twig-bundle": "6.2.*", // v6.2.5
        "symfony/ux-react": "^2.6", // v2.7.1
        "symfony/ux-vue": "^2.7", // v2.7.1
        "symfony/validator": "6.2.*", // v6.2.5
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.1
        "symfony/yaml": "6.2.*" // v6.2.5
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.3
        "symfony/browser-kit": "6.2.*", // v6.2.5
        "symfony/css-selector": "6.2.*", // v6.2.5
        "symfony/debug-bundle": "6.2.*", // v6.2.5
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.2.5
        "symfony/stopwatch": "6.2.*", // v6.2.5
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.5
        "zenstruck/browser": "^1.2", // v1.2.0
        "zenstruck/foundry": "^1.26" // v1.28.0
    }
}
userVoice