Avoid the UserInterface in your Entity

Written on Jul 18, 2017

Note: This article is (partially) copied from stovepipe.systems and modified. With thanks to the orginal author Iltar van der Berg.

One of the steps of implementing the security features of your application, involves creating an object to contain your user information such as your username, email, password and user id. If you have followed the Symfony docs, you will most likely have ended up with a User entity implementing the UserInterface. I would like to show you an alternative approach which will prevent several issues within a Symfony application.

Entity

In Symfony the user object which represents the currently authenticated user, is stored within a TokenInterface object. Internally this token is stored in the session as a serialized object and accessible via the token storage service: security.token_storage.

The most frequently used implementation is by fetching an entity from your database:

While the documentation gives a very detailed explanation and a nice example of how easy it can be, this also comes with side effects:

  • You will end up with this Entity in your session
  • Developers tend to also use this entity in forms

If you end up with Entities in your session, you will get synchronization issues. If you update your entity, that means your session entity won’t be updated as it’s not from the database. In order to solve this issue, you can merge the entity back into the entity manager each request.

While this solves one of the problems, another common issue is the (un)serialization. Eventually your User Entity will get relations to other objects and this comes with several side-effects:

  • Relations will be serialized as well
  • If a relation is lazy loaded (standard setting), it will try to serialize the Proxy which contains a connection. This will spew some errors on your screen as the connection cannot be serialized.

Oh and don’t even think about changing your Entity such as adding fields, this will cause unserialization issues with incomplete objects because of missing properties. This case is triggered for every authenticated user.

The solution to this problem is rather simple actually: Data Transfer Objects. This object’s responsibility is to feed the security system with only the information required. The sole responsibility of this object is to implement the UserInterface and provide the security system with authentication.

A Layer of Abstraction

According to the documentation, you have to implement an interface which will return an object implementing the UserInterface: the Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface. In the example this is done by adding it to your UserRepository, which is a doctrine EntityRepository.

Considering your UserRepository must return User entities, you can’t simply make it return a SecurityUser. To solve this, you have to make an object using the UserRepository creating a SecurityUser.

Recap

So what you’ve done is the following:

  • Your Entity is no longer stored in the session, thus avoiding synchronization and serialization issues
  • Your Entity and EntityRepository are no longer tightly coupled to the security system
  • Your SecurityUser now only contains data required for identification

This also means that if you request the security user from the token storage (either directly or indirectly), it will no longer contain an entity. While some may argue this is a downside, I prefer it this way. The security user contains an identifier which is related to your User object in the database. If you happen to need this Entity often, you could create an ArgumentValueResolver. This resolver would fetch the Entity based on the security user and present it in your action arguments.

An ArgumentValueResolver might look like this:

Happy authenticating! :key: