Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

make:user

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.

Independientemente de cómo se autentifiquen tus usuarios -un formulario de inicio de sesión, una autenticación social o una clave de la API-, tu sistema de seguridad necesita algún concepto de usuario: alguna clase que describa la "cosa" que ha iniciado la sesión.

Sí, el paso 1 de la autenticación es crear una clase User. ¡Y hay un comando que puede ayudarnos! Busca tu terminal y ejecuta:

symfony console make:user

Como recordatorio, symfony console es sólo un atajo para bin/console... pero como estoy usando la integración de Docker con el servidor web Symfony, llamar a symfony consolepermite al binario symfony inyectar algunas variables de entorno que apuntan a la base de datos de Docker. No importará para este comando, pero sí para cualquier comando que hable con la base de datos.

Bien, primera pregunta:

El nombre de la clase de usuario

Normalmente, será User... aunque sería mejor utilizar algo comoHumanoidEntity. Si la "cosa" que entra en tu sitio se llamaría mejor Company o University o Machine, utiliza ese nombre aquí.

¿Quieres almacenar los datos de los usuarios en la base de datos a través de Doctrine?

Para nosotros: es un sí rotundo... pero no es un requisito. Tus datos de usuario pueden estar almacenados en algún otro servidor... aunque incluso en ese caso, a menudo es conveniente almacenar algunos datos adicionales en tu base de datos local... en cuyo caso también dirías que sí aquí.

Siguiente:

Introduce un nombre de propiedad que será el nombre de visualización único para el usuario.

Yo voy a utilizar email. Esto no es tan importante, y explicaré cómo se utiliza en unos minutos. Por último:

¿Necesitará esta aplicación hacer un hash y comprobar las contraseñas de los usuarios?

Sólo tienes que decir que sí si será responsabilidad de tu aplicación comprobar la contraseña del usuario cuando se conecte. Vamos a hacer esto... pero voy a decir que no. Lo añadiremos manualmente un poco más tarde.

Pulsa enter y... ¡listo!

La clase y la entidad de usuario

Bien. ¿Qué ha hecho esto? En primer lugar, ha creado una entidad User y una UserRepository... exactamente lo mismo que se obtiene normalmente al ejecutar make:entity. Vamos a ver esa nueva clase User: src/Entity/User.php:

... lines 1 - 2
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* @ORM\Column(type="json")
*/
private $roles = [];
... lines 31 - 113
}

En primer lugar, se trata de una entidad normal y aburrida de Doctrine: tiene anotaciones -o quizás atributos de PHP 8 para ti- y un id. Es... sólo una entidad: no tiene nada de especial.

Interfaz de usuario y métodos obsoletos

Lo único que le importa a Symfony es que tu clase de usuario implementeUserInterface. Mantén pulsado Command o Ctrl y haz clic para saltar al código del núcleo para ver esto.

Esta interfaz realmente sólo tiene 3 métodos: getUserIdentifier() el que ves documentado encima de la interfaz, getRoles()... y otro más abajo llamado eraseCredentials(). Si estás confundido sobre por qué estoy omitiendo todos estos otros métodos, es porque están obsoletos. En Symfony 6, esta interfaz sólo tendrá esos 3: getUserIdentifier(), getRoles() yeraseCredentials().

En nuestra clase User, si te desplazas hacia abajo, el comando make:user implementó todo esto por nosotros. Gracias a cómo hemos respondido a una de sus preguntas,getUserIdentier() devuelve el correo electrónico:

... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 48
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
... lines 58 - 113
}

Esto... no es demasiado importante: es sobre todo una representación visual de tu objeto Usuario... se utiliza en la barra de herramientas de depuración de la web... y en algunos sistemas opcionales, como el sistema "recuérdame".

Si estás usando Symfony 5 como yo, te darás cuenta de que los métodos obsoletos se siguen generando. Son necesarios sólo por compatibilidad con versiones anteriores, y puedes eliminarlos una vez que estés en Symfony 6.

El método getRoles() se ocupa de los permisos:

... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 66
/**
* @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 78 - 113
}

más adelante se hablará de ello. Además, getPassword() y getSalt() están obsoletos:

... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 85
/**
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.
*
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): ?string
{
return null;
}
/**
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.
*
* @see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
... lines 105 - 113
}

Seguirás necesitando el método getPassword() si compruebas las contraseñas en tu sitio, pero ya lo veremos más adelante. Por último, eraseCredentials() forma parte deUserInterface:

... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 105
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}

pero no es muy importante y también hablaremos de ello más adelante.

Así que a alto nivel... si ignoras los métodos obsoletos... y el no tan importanteeraseCredentials(), lo único que debe tener nuestra clase User es un identificador y un método que devuelva la matriz de roles que debe tener este usuario. Sí... es sobre todo una entidad de User.

"proveedores": El proveedor de usuarios

El comando make:user también hizo un ajuste en nuestro archivo security.yaml: puedes verlo aquí:

security:
... lines 2 - 7
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
... lines 14 - 33

Añadió lo que se llama un "proveedor de usuario", que es un objeto que sabe cómo cargar tus objetos de usuario... ya sea que cargues esos datos desde una API o desde una base de datos. Como estamos usando Doctrine, podemos usar el proveedor incorporado entity: sabe cómo obtener nuestros usuarios de la base de datos usando la propiedad email.

Quería que vieras este cambio... pero el proveedor de usuarios no es importante todavía. Te mostraré exactamente cómo y dónde se utiliza a medida que avancemos.

A continuación: tenemos un control total sobre el aspecto de nuestra clase User. ¡El poder! Así que vamos a añadirle un campo personalizado y a cargar nuestra base de datos con un buen conjunto de usuarios ficticios.

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