Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Roles dinámicos

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

Antes hemos hablado de cómo, en el momento en que un usuario se conecta, Symfony llama al método getRoles()en el objeto User para averiguar qué roles tendrá ese usuario:

... lines 1 - 12
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
... lines 15 - 26
/**
* @ORM\Column(type="json")
*/
private $roles = [];
... lines 31 - 78
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
... lines 90 - 154
}

Este método lee una propiedad de la matriz $roles que está almacenada en la base de datos como JSON... y luego añade siempre ROLE_USER a la misma.

Hasta ahora, no hemos dado a ningún usuario ningún papel adicional en la base de datos... así que todos los usuarios tienen sólo ROLE_USER. Puedes ver esto en la barra de herramientas de depuración de la web: haz clic para saltar al perfilador. Sí, tenemos ROLE_USER.

Esto es demasiado aburrido... ¡así que vamos a añadir algunos verdaderos usuarios administradores! Primero, abreconfig/packages/security.yaml... y, debajo de access_control, cambia esto para requerir de nuevo ROLE_ADMIN:

security:
... lines 2 - 50
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
... lines 53 - 54

Recuerda: los roles son sólo cadenas que inventamos... pueden ser cualquier cosa: ROLE_USER ROLE_ADMIN ROLE_PUPPY, ROLE_ROLLERCOASTER... lo que sea. La única regla es que deben empezar por ROLE_. Gracias a esto, si vamos a /admin... ¡acceso denegado!

Rellenar los roles en la base de datos

Vamos a añadir algunos usuarios administradores a la base de datos. Abre la clase de accesorios:src/DataFixtures/AppFixtures.php. Veamos... aquí abajo, vamos a crear un usuario personalizado y luego 10 usuarios aleatorios. Haz que este primer usuario sea un administrador: ponroles en una matriz con ROLE_ADMIN:

... lines 1 - 15
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
... lines 20 - 47
UserFactory::createOne([
'email' => 'abraca_admin@example.com',
'roles' => ['ROLE_ADMIN']
]);
... lines 52 - 57
}
}

Vamos a crear también un usuario normal que podamos utilizar para iniciar la sesión. Copia el código de UserFactory, pégalo, usa abraca_user@example.com... y deja roles vacío:

... lines 1 - 15
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
... lines 20 - 47
UserFactory::createOne([
'email' => 'abraca_admin@example.com',
'roles' => ['ROLE_ADMIN']
]);
UserFactory::createOne([
'email' => 'abraca_user@example.com',
]);
... lines 55 - 57
}
}

¡Hagámoslo! En tu terminal, ejecuta:

symfony console doctrine:fixtures:load

Cuando termine... gira y actualiza. ¡Hemos cerrado la sesión! Eso es porque, al cargar el usuario desde la sesión, nuestro proveedor de usuarios intentó refrescar el usuario desde la base de datos... pero el antiguo usuario con su antiguo id había desaparecido gracias a las fijaciones. Vuelve a entrar en .... con la contraseña tada y... ¡acceso concedido! ¡Estamos de enhorabuena! Y en el perfilador, tenemos los dos roles.

Comprobación del acceso dentro de Twig

Además de comprobar o imponer los roles a través de access_control... o desde dentro de un controlador, a menudo también necesitamos comprobar los roles en Twig. Por ejemplo, si el usuario actual tiene ROLE_ADMIN, pongamos un enlace a la página del administrador.

Abre templates/base.html.twig. Justo después de este enlace de respuestas... así que déjame buscar "respuestas"... ahí vamos, añade if, y luego utiliza una función especial de is_granted() para comprobar si el usuario tiene ROLE_ADMIN:

<!DOCTYPE html>
<html>
... lines 3 - 14
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-1">
<div class="container-fluid">
... lines 18 - 26
<div class="collapse navbar-collapse" id="navbar-collapsable">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
... lines 29 - 31
{% if is_granted('ROLE_ADMIN') %}
... lines 33 - 35
{% endif %}
</ul>
... lines 38 - 40
</div>
</div>
</nav>
... lines 44 - 48
</body>
</html>

¡Es así de fácil! Si es así, copia el enlace nav aquí arriba... pega... envía al usuario a admin_dashboard y di "Admin":

<!DOCTYPE html>
<html>
... lines 3 - 14
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-1">
<div class="container-fluid">
... lines 18 - 26
<div class="collapse navbar-collapse" id="navbar-collapsable">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
... lines 29 - 31
{% if is_granted('ROLE_ADMIN') %}
<li class="nav-item">
<a class="nav-link" href="{{ path('admin_dashboard') }}">Admin</a>
</li>
{% endif %}
</ul>
... lines 38 - 40
</div>
</div>
</nav>
... lines 44 - 48
</body>
</html>

Cuando refresquemos... ¡ya está!

Hagamos lo mismo con los enlaces "iniciar sesión" y "registrarse": sólo los necesitamos si no estamos conectados. Aquí abajo, para comprobar simplemente si el usuario está conectado, utilizais_granted('ROLE_USER')... porque, en nuestra aplicación, todos los usuarios tienen al menos ese rol. Añade else, endif, y luego haré una sangría. Si hemos iniciado la sesión, podemos pegar para añadir un enlace "Cerrar sesión" que apunte a la ruta app_logout:

<!DOCTYPE html>
<html>
... lines 3 - 14
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-1">
<div class="container-fluid">
... lines 18 - 26
<div class="collapse navbar-collapse" id="navbar-collapsable">
... lines 28 - 38
{% if is_granted('ROLE_USER') %}
<a class="nav-link text-black-50" href="{{ path('app_logout') }}">Log Out</a>
{% else %}
<a class="nav-link text-black-50" href="{{ path('app_login') }}">Log In</a>
<a href="#" class="btn btn-dark">Sign up</a>
{% endif %}
</div>
</div>
</nav>
... lines 48 - 52
</body>
</html>

¡Genial! Refresca y... mucho mejor. ¡Esto parece un sitio real!

A continuación, vamos a conocer unas cuantas "cadenas" especiales que puedes utilizar con la autorización: cadenas que no empiezan por ROLE_. Utilizaremos una de ellas para mostrar cómo podemos denegar fácilmente el acceso a todas las páginas de una sección excepto a una.

Leave a comment!

¡Este tutorial también funciona muy bien para Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.3", // v3.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/annotations": "^1.0", // 1.13.2
        "doctrine/doctrine-bundle": "^2.1", // 2.6.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
        "doctrine/orm": "^2.7", // 2.10.1
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.1
        "pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
        "pagerfanta/twig": "^3.3", // v3.3.0
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "scheb/2fa-bundle": "^5.12", // v5.12.1
        "scheb/2fa-qr-code": "^5.12", // v5.12.1
        "scheb/2fa-totp": "^5.12", // v5.12.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.0
        "stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
        "symfony/asset": "5.3.*", // v5.3.4
        "symfony/console": "5.3.*", // v5.3.7
        "symfony/dotenv": "5.3.*", // v5.3.8
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/form": "5.3.*", // v5.3.8
        "symfony/framework-bundle": "5.3.*", // v5.3.8
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/property-access": "5.3.*", // v5.3.8
        "symfony/property-info": "5.3.*", // v5.3.8
        "symfony/rate-limiter": "5.3.*", // v5.3.4
        "symfony/runtime": "5.3.*", // v5.3.4
        "symfony/security-bundle": "5.3.*", // v5.3.8
        "symfony/serializer": "5.3.*", // v5.3.8
        "symfony/stopwatch": "5.3.*", // v5.3.4
        "symfony/twig-bundle": "5.3.*", // v5.3.4
        "symfony/ux-chartjs": "^1.3", // v1.3.0
        "symfony/validator": "5.3.*", // v5.3.8
        "symfony/webpack-encore-bundle": "^1.7", // v1.12.0
        "symfony/yaml": "5.3.*", // v5.3.6
        "symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.3
        "twig/string-extra": "^3.3", // v3.3.3
        "twig/twig": "^2.12|^3.0" // v3.3.3
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.3.*", // v5.3.4
        "symfony/maker-bundle": "^1.15", // v1.34.0
        "symfony/var-dumper": "5.3.*", // v5.3.8
        "symfony/web-profiler-bundle": "5.3.*", // v5.3.8
        "zenstruck/foundry": "^1.1" // v1.13.3
    }
}