Error con Symfony2: You cannot refresh a user from the EntityUserProvider that does not contain an identifier

marzo 18, 2012

He creado dos bundles, uno llamado UserBundle y otro ProfileBundle. El bundle UserBundle es totalmente independiente y reutilizable en cualquier proyecto (contando con funcionalidades tales como registro, login, logout, recordar contraseña, confirmar usuario mediante email, etc.) con la única pega de que solamente se guardan el email y la contraseña del usuario. Hay proyectos que solo requieren estos datos y no merece la pena «ensuciarlo» con más.

El bundle ProfileBundle subsana esta limitación y otorga al usuario un perfil con su nombre, apellidos, etc. El registro se hace desde el UserBundle y, una vez logueado en la aplicación, completas tu perfil de usuario mediante el ProfileBundle.

Al terminar de completar tu perfil y pulsar el botón guardar, el usuario logueado se recarga con los nuevos datos introducidos. Al hacer esto, me aparecía el siguiente error:

You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.

Y en otras ocasiones también se mostraba este otro error:

SymfonyComponentSecurityCoreAuthenticationTokenUsernamePasswordToken::serialize() must return a string or NULL

La solución es sencilla y solo hay que fijarse en el mensaje de error. Si el usuario se va a recargar una vez ya está logueado, la entidad Usuario debe implementar la clase Serializable y definir sus métodos serialize() y unserialize().

En diferentes blogs que hablan sobre Symfony2, en los métodos serialize y unserialize hacen uso del método getUsername() lo cual puede ser un error. Lo muestro con un ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class User implements UserInterface, Serializable
{
 
    /**
     * @var integer $id
     *
     * @ORMColumn(name="id", type="integer")
     * @ORMId
     * @ORMGeneratedValue(strategy="AUTO")
     */
    private $id;
 
    /**
     * @var string $email
     *
     * @ORMColumn(name="email", type="string", length=255, unique=true)
     * @AssertEmail()
     */
    private $email;
 
    [...]
 
    function getUsername()
    {
        return $this->getEmail();
    }
 
    public function serialize()
    {
       return serialize($this->getUsername());
    }
 
    public function unserialize($data)
    {
        $this->email = unserialize($data);
    }
}

En el ejemplo anterior vemos que la propiedad $email no es la clave primaria y, aunque hayamos marcado que es única, nos seguirá apareciendo el error anterior ya que Doctrine necesita que el refresh del usuario se haga mediante su identificador único (en algún caso podría llegar a ser el email, pero no en este ejemplo).

Solamente tendremos que cambiar el código anterior por este:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class User implements UserInterface, Serializable
{
 
    /**
     * @var integer $id
     *
     * @ORMColumn(name="id", type="integer")
     * @ORMId
     * @ORMGeneratedValue(strategy="AUTO")
     */
    private $id;
 
    /**
     * @var string $email
     *
     * @ORMColumn(name="email", type="string", length=255, unique=true)
     * @AssertEmail()
     */
    private $email;
 
    [...]
 
    function getUsername()
    {
        return $this->getEmail();
    }
 
    public function serialize()
    {
       return serialize($this->getId());
    }
 
    public function unserialize($data)
    {
        $this->id = unserialize($data);
    }
}

El código anterior se puede optimizar y se podrían añadir más propiedades a los métodos serialize y unserialize (se podrían utilizar en conjunto las propiedades id, email, etc.).

Tags ; , , , , , , , ,

4 comentarios

    Francesc Rosàs Mar 21, 2012

    La verdad es que esta es la parte que menos me está gustando de Symfony. ¿Porqué se serializa todo el UserInterface si con el username ya es suficiente?

    Como apunte a tu solución sólo añadir que no hace falta serializar más propiedades que las de la clave primária a no ser que se quiera usar el serialize() en otros contextos.

    Ah, otra solución simple es hacer que todas las propiedades de la entidad que implementa el UserInterface y sus «foreign keys» sean protected, aunque esto implica que puedes acabar serializando innecesariamente un montón de datos.

    Responder
    Jon Mar 21, 2012

    Gracias por el comentario! 🙂

    Decía lo de serializar más propiedades porque lo he visto en algún que otro ejemplo. Yo de momento no lo he necesitado.

    Serializar por serializar es tontería xD

    Responder
    Juan Nov 19, 2012

    A mi si me fue muy útil, dado que dentro de mi entidad de usuario tengo una relación manytoone con otra entidad.

    Gracias!

    Responder

Escribe un comentario

Los comentarios son moderados y se utiliza rel="nofollow" para los enlaces.