Search code examples
phpsymfonysymfony5symfony-security

Expected an instance of "Symfony\Component\Security\Core\User\UserInterface" as first argument


I'm trying to add hashing to passwords for my users, I followed this guide for symfony 5.3 and when I'm using

->setPassword($passwordHasher->hashPassword(
                $user,
                'contraseña'
            ))

While testing if it works, I'm getting the error:

Expected an instance of "Symfony\Component\Security\Core\User\UserInterface" as first argument, but got "App\Entity\Usuario".

I don't understand because it's literally written as the guide shows.
These are my files:

User entity

namespace App\Entity;

use App\Repository\UsuarioRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass=UsuarioRepository::class)
 */
class Usuario
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $nombre;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $apellidos;

    /**
     * @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;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $prefijo;

    /**
    * @ORM\Column(type="string", length=255)
    */
    private $telefono;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Returning a salt is only needed, if you are not using a modern
     * hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
     *
     * @see UserInterface
     */
    public function getSalt(): ?string
    {
        return null;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function getNombre(): ?string
    {
        return $this->nombre;
    }

    public function setNombre(string $nombre): self
    {
        $this->nombre = $nombre;

        return $this;
    }

    public function getApellidos(): ?string
    {
        return $this->apellidos;
    }

    public function setApellidos(string $apellidos): self
    {
        $this->apellidos = $apellidos;

        return $this;
    }

    public function getPrefijo(): ?string
    {
        return $this->prefijo;
    }

    public function setPrefijo(string $prefijo): self
    {
        $this->prefijo = $prefijo;

        return $this;
    }

    public function getTelefono(): ?string
    {
        return $this->telefono;
    }

    public function setTelefono(string $telefono): self
    {
        $this->telefono = $telefono;

        return $this;
    }
}

Controller

namespace App\Controller;

use App\Entity\RegistroUsuario;
use App\Form\RegistroUsuarioForm;
use App\Repository\PrefijoTfnoPaisesRepository;
use App\DataFixtures\UsuarioFixtures;
use App\Entity\Usuario;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

/**
     * @Route("/usuario", name="usuario")
     */
class UsuarioController extends AbstractController
{
    /**
     * @Route("/", name="usuario")
     */
    public function index(Request $request, 
                        PrefijoTfnoPaisesRepository $prefijoTfnoPaisesRepository, 
                        UserPasswordHasherInterface $passwordHasher)
    {
    
        $paramRegistroForm = new RegistroUsuario();
        $registroFrom = $this->createForm(RegistroUsuarioForm::class, $paramRegistroForm);
        $registroFrom->handleRequest($request);
        $paisesPrefijo = $prefijoTfnoPaisesRepository->findAll();

        if ($registroFrom->isSubmitted() && $registroFrom->isValid()){
            $manager = $this->getDoctrine()->getManager();
            // encode the plain password
            
            $user = new Usuario();
            $user
            ->setNombre("nombre")
            ->setApellidos("apellidos")
            ->setEmail("[email protected]")
            ->setRoles(["rol"])
            ->setPassword($passwordHasher->hashPassword(
                $user,
                'contraseña'
            ))
            ->setPrefijo("+34")
            ->setTelefono("telefono")
            ;
            $manager->persist($user);
            
            $manager->flush();     
    
            dd($request->request->get('registro_usuario_form'));
        }       
    
        
        return $this->render('usuario/index.html.twig', [
            'registroFormulario' => $registroFrom->createView(),
            'paisesPrefijo' => $paisesPrefijo
        ]);
    }
}

security.yaml

security:
    password_hashers:
        App\Entity\Usuario:
            # Use native password hasher, which auto-selects the best
            # possible hashing algorithm (starting from Symfony 5.3 this is "bcrypt")
            algorithm: auto


Solution

  • You Usuario entity should implement UserInterface.

    It's a very basic and simple interface:

    interface UserInterface
    {
        public function getRoles();
        public function getPassword();
        public function getSalt();
        public function eraseCredentials();
        public function getUsername();
    }
    

    You've implemented the necessary methods, but didn't declare the interface implementation:

    class Usuario implements UserInterface 
    { /* class implementation */ }
    

    For forward compatibility, you should also implement getIdentifier() (getUsername() is going to be removed from the interface on Symfony 6, and be replaced with this method).

    Simply:

    class Usuario implements UserInterface
    {
        public function getIdentifier():string
        {
            return $this->email;
        }
    
        // rest of the implementation follows
    
    }
    

    Additionally, since your Usuario entity also implements getPassword() from UserInterface, you should also implement PasswordAuthenticatedUserInterface. This new interface was introduced in 5.3, and in 6.0 getPassword() will be removed from UserInterface:

    class Usuario implements UserInterface, PasswordAuthenticatedUserInterface
    {
    }
    

    By the time 6.0 you'll probably want to get rid of getSalt() as well, since it will be removed from UserInterface (and moved to LegacyPasswordAuthenticatedUserInterface()), but it's very unlikely the method is of any use in your application (since any relatively modern hashing method will produce its own random salt).