'Symfony UserInterface is serializing the entire massive User entity
We implement AdvancedUserInterface to manage user authentication, but for some reason Symfony security is serializing the entire User entity instead of just the minimum required fields (e.g. id, username, password).
According to the documentation, we need to specify the exact fields that should be serialized (and then the rest of the fields will be ignored).
class User implements AdvancedUserInterface, \Serializable {
/**
* @see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password
));
}
}
But although we are doing that, Symfony security is ignoring this serialize() method and still serializing the ENTIRE entity. This breaks the sign in because the object becomes too big to serialize and store.
Solution 1:[1]
Evidently Symfony security uses Symfony\Component\Security\Core\Authentication\Token\AbstractToken and this has a custom method for serialize that adds more data into the session serialized User.
public function serialize()
{
return serialize(
array(
is_object($this->user) ? clone $this->user : $this->user,
$this->authenticated,
$this->roles,
$this->attributes,
)
);
}
This adds role to the serialized object. But we have a custom role system that has associations with other entities (e.g. Site) which cause the fatal bloating of the User when it is serialized.
Solution 2:[2]
Here is an explanation of how it works in Symfony 5.4 and later https://symfony.com/doc/5.4/security.html#understanding-how-users-are-refreshed-from-the-session. However, they mention SerializableInterface, without any reference and had a hard time finding anything about it. Such an interface doesn't exist (PhpStorm is not able to find it). I assumed they probably mean https://www.php.net/manual/en/class.serializable.php.
I solved this problem by implementing \Serializable interface in my User entity as follows:
# App\Entity\User
public function serialize()
{
return serialize([
'id' => $this->getId(),
'password' => $this->getPassword(),
'email' => $this->getEmail(),
'userIdentifier' => $this->getEmail(),
'username' => $this->getUsername(),
'salt' => $this->getSalt(),
'roles' => $this->getRoles(),
'enabled' => $this->isEnabled(),
]);
}
public function unserialize($data)
{
$unserialized = unserialize($data);
$this
->setId($unserialized['id'])
->setPassword($unserialized['password'])
->setEmail($unserialized['email'])
->setRoles($unserialized['roles'])
->setEnabled($unserialized['enabled']);
}
I no longer save unwanted relations or data. The session is now small. I also noticed a significant response time decrease = faster page load for logged-in users, especially admins.
Solution 3:[3]
I realize this is an old question, but I solved a problem similar to this using the Symfony serializer with groups. You can read more about this here.
The function that encodes the groups looks like:
public static function encodeCategory($objects)
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$encoders = [new JsonEncoder()];
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
if(method_exists($object,'getName'))
return $object->getName();
},
];
$normalizers = [new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext)];
$serializer = new Serializer($normalizers,$encoders);
return $serializer->serialize($objects,'json',['groups' => 'category']);
}
A simple entities example is this:
class User
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"category"})
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
* @Groups({"category"})
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Type", inversedBy="users")
* @ORM\JoinColumn(nullable=false)
* @Groups({"category"})
*/
private $type;
}
And the Type entity:
class Type
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"category"})
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
* @Groups({"category"})
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="App\Entity\User", mappedBy="type")
*/
private $users;
}
You can extend this for as many entities as you like, or create new groups. And now use the function:
$users = $this->getDoctrine()->getRepository(User::class)->findAll();
return new JsonResponse($this->encodeCategory($users));
Before using groups, I tried the MaxDepth annotation, but it seems this has some problems.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Chadwick Meyer |
| Solution 2 | Tomáš Tibenský |
| Solution 3 | BogdanG |
