In this blog post I will show how to decouple your core application from the Symfony Security component and User bundles such as the FOSUserBundle.
In my opinion a framework has to be evaluated by how much it allows you to hide it from your actual application. This blog post will add another perspective on how to achieve decoupling from Symfony user and security with a very simple approach. With this puzzle piece and others I wrote about before (Controllers as Service, Param Converters) and other pieces I still need to write about, I would classify Symfony2 as a good framework.
A number of other authors have written blog posts on decoupling Symfony and your model code as well, such as William Durand on project structure , Matthias Verraes on project structure as well and on Decoupling Forms from Entities.
User management breaks the isolation of your business model open,
by introducing the UserInterface
from the Security component into your
code base. In combination with the FOSUserBundle this can cause a dependency
on quite a bit of code already because I wouldn't call the FOS\UserBundle\Model\User
object
leightweight. However this and other FriendsOfSymfony bundles
are very helpful to get annoying features of your application done in a matter
of hours.
You can decouple the FOSUserBundle from our own entities and code by
introducing a second object that representes the user concept in our model.
The UserInterface
entities need fields for username, password, enabled and
some more. Except the username and email, the business model rarely needs other
properties.
Take an ecommerce system as example, where customers can register as users.
In this example we could create two entities MyProject\UserBundle\Entity\User
and
MyProject\ShopBundle\Entity\Customer
. Two strategies exist now:
- Use the same table for both entities with some shared and other exclusive columns
- Use different tables in the database
I haven't tried the first option yet, so I cannot say much about the feasibility.
For case two, the User
entity looks like this:
<?php
// src/MyProject/UserBundle/Entity/User.php;
namespace MyProject\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
The Customer
entity is a completly new object, not depending
on the UserInterface
and containing some duplicated properties.
You can either use exactly the same ID or plant the $userId
as
correlation ID in the Customer
object.
<?php
// src/MyProject/ShopBundle/Entity/Customer.php
namespace MyProject\ShopBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="customer")
*/
class Customer
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="NONE")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $username;
}
Now you need to extend the application to synchronize all changes from the
User
entity to the Customer
entity. You can
achieve this relatively easy by overwriting the ModelManager
or when finally
supported by FOSUserBundle listening to events. Your own code can
exclusively work with Customer
objects now, void of any reference to
the Symfony Security component or the huge FOSUserBundle dependency.