Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Creación de la entidad de usuario

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

No vamos a hablar específicamente de la seguridad en este tutorial; lo haremos en nuestro próximo curso y le prestaremos la debida atención. Pero, incluso olvidando la seguridad y el inicio de sesión y todo eso, es muy probable que tu API tenga algún concepto de "usuarios". En nuestro caso, un "usuario" publicará un listado de quesos y se convertirá en su "propietario". Y puede que después, para comprar un listado de quesos, un usuario envíe un mensaje a otro usuario. Es hora de llevar nuestra aplicación al siguiente nivel creando esa entidad.

make:usuario

Y aunque te diga que no pienses en la seguridad, en lugar de crear la entidad usuario con make:entity como haría normalmente, voy a utilizar make:user,

php bin/console make:user

Sí, esto configurará algunas cosas relacionadas con la seguridad... pero nada que vayamos a utilizar todavía. Mira la parte 2 de esta serie para ver todas esas cosas.

En cualquier caso, llama a la clase User, y quiero almacenar los usuarios en la base de datos. Para el nombre único de visualización, voy a hacer que los usuarios se registren a través del correo electrónico, así que usa eso. Y entonces:

¿Esta aplicación necesita hacer un hash o comprobar las contraseñas de los usuarios?

Hablaremos más de esto en el tutorial de seguridad. Pero si los usuarios van a tener que iniciar sesión en tu sitio a través de una contraseña y tu aplicación va a ser la responsable de comprobar si esa contraseña es válida -no te limitas a enviar la contraseña a algún otro servicio para que la verifique-, entonces responde que sí. No importa si el usuario va a introducir la contraseña a través de una aplicación de iPhone que habla con tu API o a través de un formulario de inicio de sesión: responde que sí si tu aplicación es responsable de gestionar las contraseñas de los usuarios.

Utilizaré el hashtag de contraseñas Argon2i. Pero si no ves esta pregunta, ¡no pasa nada! A partir de Symfony 4.3, no es necesario que elijas un algoritmo de hashing de contraseñas porque Symfony puede elegir el mejor disponible de forma automática. Algo realmente genial.

¡Vamos a ver qué hace esto! Me alegra decir que... ¡no mucho! En primer lugar, ahora tenemos una entidadUser. Y... no tiene nada de especial: tiene algunos métodos adicionales relacionados con la seguridad, como getRoles(), getPassword(), getSalt()y eraseCredentials(), pero no afectarán a lo que estamos haciendo. En su mayor parte, tenemos una entidad normal y aburrida con $id, $email, una propiedad de matriz $roles, y $password, que finalmente almacenará la contraseña con hash.

... lines 1 - 2
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
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 = [];
/**
* @var string The hashed password
* @ORM\Column(type="string")
*/
private $password;
... lines 35 - 112
}

Esto también ha creado la entidad normal UserRepository y ha hecho un par de cambios ensecurity.yaml: ha creado encoders - esto podría decir auto para ti, gracias a la nueva característica de Symfony 4.3 - y el proveedor de usuarios. Cosas de las que hablaremos más adelante. Así que... olvida que están aquí y en su lugar di... ¡vaya! ¡Tenemos una entidadUser!

security:
encoders:
App\Entity\User:
algorithm: argon2i
... lines 5 - 6
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
... lines 13 - 33

... lines 1 - 2
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, User::class);
}
... lines 21 - 49
}

Añadiendo el campo de nombre de usuario

Gracias al comando, la entidad tiene una propiedad email, y pienso hacer que los usuarios se registren utilizando eso. Pero también quiero que cada usuario tenga un "nombre de usuario" que podamos mostrar públicamente. Vamos a añadirlo: busca tu terminal y ejecuta

php bin/console make:entity

Actualiza User y añade username como string, 255, no anulable en la base de datos, y pulsa intro para terminar.

Ahora abre User... y desplázate hasta getUsername(). El comando make:usergeneró esto y devolvió $this->email... porque eso es lo que elegí como mi nombre "para mostrar" por seguridad. Ahora que realmente tenemos un campo de nombre de usuario, devuelve $this->username.

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 35
/**
* @ORM\Column(type="string", length=255)
*/
private $username;
... lines 40 - 62
public function getUsername(): string
{
return (string) $this->username;
}
... lines 67 - 124
}

Ah, y mientras hacemos esta clase, simplemente, increíble, el comando make:user sabía que email debía ser único, así que añadió unique=true. Añadamos también eso a username: unique=true.

... lines 1 - 35
/**
* @ORM\Column(type="string", length=255, unique=true)
*/
private $username;
... lines 40 - 126

¡Esa es una bonita entidad! Vamos a sincronizar nuestra base de datos ejecutando:

php bin/console make:migration

Muévete... y vuelve a comprobar el SQL: CREATE TABLE user - ¡se ve bien!

... lines 1 - 12
final class Version20190509185722 extends AbstractMigration
{
... lines 15 - 19
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
}
... lines 27 - 34
}

Ejecútalo con:

php bin/console doctrine:migration:migrate

¡Perfecto! Tenemos una nueva y preciosa entidad Doctrine... pero en lo que respecta a la Plataforma API, seguimos teniendo sólo un recurso API: CheeseListing.

Lo siguiente: vamos a exponer User como un Recurso API y a utilizar todos nuestros nuevos conocimientos para perfeccionar ese nuevo recurso en... unos 5 minutos.

Leave a comment!

20
Login or Register to join the conversation
Steven L. Avatar
Steven L. Avatar Steven L. | posted hace 3 años | edited

If you're running mariadb and getting the error "SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes" when running the migration, edit User.php and limit the username length to 191:
` /**

 * @ORM\Column(type="string", length=191, unique=true)
 */
private $username;`
1 Reply

Hey Steve,

Thanks for this tip!

Cheers!

Reply
Jonas Avatar

Using the latest Symfony binary and following along the tutorial for API Platform (which is still based on Symfony 4/5), I get the following error:

>bin/console make:entitiy   

In SecurityExtension.php line 307:
                                                                       
  Invalid firewall "main": user provider "users_in_memory" not found.  

It seems that the command bin/console make:user replaces

    providers:
        users_in_memory: { memory: null }

with

    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email

but leaves

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            provider: users_in_memory

intact. We no longer have a users_in_memory provider, which leads to the error.

The make:user command should probably result in a coherent state of the security.yaml file, so either users_in_memory or app_user_provider, not a mix of both.

TL:DR:

Replace provider: users_in_memory with provider: app_user_provider in security.yaml under security:firewalls:main

I hope this does not cause problems down the road, but so far everything worked as expected.

Reply

Hi Jonas!

Thanks for posting this! It seems like a bug in MakerBundle - it's possible it's still a bug, or it may have been fixed (I work on MakerBundle, but we do so much that I can't remember when we do things 🙃). Anyways - thanks for posting - and if you ARE using the latest version of MakerBundle, it would be awesome if you could open an issue about this on https://github.com/symfony/maker-bundle

Cheers!

Reply
Jonas Avatar

Hi Ryan, thanks for the quick answer!

My maker bundle version is not current, as I followed the advice in chapter 2 and installed version 1.11.

So maybe it is fixed, just not if you're following along with the course. :)

All the best,

Jonas

Reply
André P. Avatar
André P. Avatar André P. | posted hace 2 años | edited

If you're using Symfony 5.3, you'll get the following error when trying to add new fields to the User entity:

PHP Fatal error: Class App\Entity\User contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Symfony\Component\Security\Core\User\UserInterface::getUsername) in /mnt/c/laragon/www/api-platform/src/Entity/User.php on line 13

This is because the method getUsername was deprecated in favor of getUserIdentifier, but the UserInterface still implements the old method, not the new one.

You can find more information here: <a href="https://github.com/symfony/symfony/pull/41493&quot;&gt;https://github.com/symfony/symfony/pull/41493&lt;/a&gt;.

Just add the following code and then try again:
`
/**

 * @deprecated in Symfony 5.3
 */
public function getUsername(): string
{
 return $this->getUserIdentifier();
}

`

Reply

Hey Andre,

Thank you for sharing the solution with others! Looks like you also need to implement that 1 abstract method as well as I can see from the error message. I think we will add a note about it, thanks!

Cheers!

Reply

Yep, this is my bad. Well, once 5.3.1 came out, we needed to merge and tag this PR- https://github.com/symfony/...

That's my job - I've been really busy, but it will happen soon!

Cheers!

Reply
Mohammed Avatar
Mohammed Avatar Mohammed | posted hace 2 años

Hello!

I'm using MongoDB instead of MySQL and I figured I couldn't use the make command to create a Document.
But I wanted to use the Symfony built-in authentication mechanism with json_login.

Is it possible for it to work by simply replacing Entity with Document in the app_user_provider?
Thanks!

Reply

Hey @Med!

Yep! Symfony's security system doesn't care if your User object is loaded via Doctrine ORM, Doctrine ODM or via an alien spaceship ;). So that's good. However, I don't believe that the ODM integrates *that* well - you can't just change the config. But, it's still pretty easy:

1) Create a class called UserProvider and make it implement UserProviderInterface
2) Fill in the methods - loadUserByUsername() is the key method that json_login will call
3) Pass your service id (well, class name) to the "providers" config in security.yaml

My answer is a big vague, because the docs are here! https://symfony.com/doc/cur...

Let me know if that helps!

Cheers!

Reply
sadikoff Avatar sadikoff | SFCASTS | posted hace 4 años | edited

Hi there

Could you please share your mysql(mariadb) server version?

Cheers!

Reply
hanen Avatar

10.1.3 mariaDB
The JSON alias was added in maria DB 10.2.7 so I have to upgrade mariaDB? is that correct

Reply

in this case you have 2 options.
First is to upgrade your server
Second is more interesting you can try configure doctrine.dbal.server_version to correspond you server version
Example:


doctrine:
    dbal:
        server_version: 'mariadb-10.1.3'

Hope it will help you, Cheers!

Reply
hanen Avatar

I choose the second option and I changed the server version but I got the following error: SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: NO)
I added a password in .env file DATABASE_URL=mysql://root:0000@127.0.0.1:3306/Glabre.
An exception occurred in driver: SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)

1 Reply

Hey hanen

Oh I'm so sorry for late message, I missed your comment at all =(
I hope you you solved this issue, because honestly I have no Idea why this error occurred, Probably it was some user password misconfiguration

Cheers!

1 Reply
hanen Avatar

I finally fixed it ;)

Reply

Great! If it's not a secret, what was the solution?

Reply
hanen Avatar

upgrading mariadb from 10.1 to 10.2.x & of course setup a new MySQL root user password by default run as administrator :))))

Reply

Awesome! Sounds like an easy fix =)

Cheers!

Reply
hanen Avatar

;))
Serveur : 127.0.0.1 via TCP/IP
Type de serveur : MariaDB
Connexion au serveur : SSL n'est pas utilisé Documentation
Version du serveur : 10.2.9-MariaDB - mariadb.org binary distribution
Version du protocole : 10
Utilisateur : root@localhost

Reply
Cat in space

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

Este tutorial funciona muy bien para Symfony 5 y la Plataforma API 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9
    }
}
userVoice