msgbartop
Desarrollador Web, Android y iOS
msgbarbottom

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

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:

Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken::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
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
 
    /**
     * @var string $email
     *
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     * @Assert\Email()
     */
    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
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
 
    /**
     * @var string $email
     *
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     * @Assert\Email()
     */
    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.).

Etiquetas: , , , , , , , ,

Integrar Sphinx en Symfony2 con Doctrine2

En el grupo de Symfony2 hay una gran cantidad de preguntas entre las que, de vez en cuando, podemos encontrar un buen hilo donde se trata un tema interesante. Entre tanta “paja” (preguntas fáciles de responder solamente leyendo la documentación) es posible que pasemos por alto alguna de estas buenas preguntas junto a sus respuestas.

Voy a intentar recopilar este tipo de hilos y traducirlos al castellano, siempre dejando el enlace al hilo original del grupo de Symfony2 por si hay alguna nueva respuesta.

Para empezar, podemos encontrar como utilizar Sphinx haciendo uso de la api como un servicio en Symfony2.

1) Nos descargamos la api de Sphinx de la página oficial http://sphinxsearch.com/, colocándolo en la carpeta vendor/sphinx-client. Debemos renombrar la clase a SphinxClient.

2) Lo añadimos en el autoload.php

  $loader->registerPrefixes(array(
   // ...
   'Sphinx'           => __DIR__.'/../vendor/sphinx-client',
  ));

3) Añadimos Sphinx como servicio de Symfony2 en el fichero config.yml

 services:
     search:
         class: SphinxClient
         calls:
             - [setArrayResult, [true]]
             - [setLimits, [0, 20, 1000]]

4) Por último, podemos hacer uso del servicio desde el controlador de esta forma:

$search  = $this->get('search');
$results = $search->Query($search_terms, 'myindex');

Evidentemente deberemos leer la documentación de Sphinx para instarlo y configurarlo correctamente. La página oficial es bastante completa en ese sentido.

Hilo original: doctrine2 and sphinx

Etiquetas: , , , , ,