Symfony2: Implementing ACL rules in your Data Fixtures
Tagged with: [ doctrine ] [ PHP ] [ proxy ] [ symfony2 ]
Doctrine’s DataFixtures are a great way to add test data to your application. It’s fairly easy to get this going: Create a fixureLoader that extends Doctrine\Common\DataFixtures\AbstractFixture, had a load() method and off you go. However, sometimes you want your data also to be protected by Symfony 2’s ACL layer. Since there isn’t a common way to do this, here is one way on how I implemented this
One of the drawbacks of fixtures is that by default haven’t got the container so it’s not possible to get other stuff
easily. However, it’s really easy to fix this. All you need to do is implement the ContainerAware interface
, and create
a setter:
If your fixtureLoader implements the ContainerAwareInterface
, it will automatically inject the container through the
setter method (setContainer()). There is no need to change any DI configuration for this. So from this point, we have a
container we can easily use.
Next up, we can define some fixtures and add some ACL to them in the normal manner:
And this pretty much works. We have defined a user entity and a blogpost entity. In the ACL we have set the user to be an operator of the object (can view,edit,delete etc). Users with a ROLE_ADMIN role are master of the blogpost (can do everything an operator can, but also grant rights to others).
In order to get things running you use the following commands:
# php app/console doctrine:database:create
# php app/console doctrine:schema:create
# php app/console init:acl
# php app/console doctrine:fixtures:load
which will create the database, the schema, the acl schema’s and load the fixtures.
There is a big catch though when you are working with fixtures loaded from multiple files. This is quite a normal way to
setup your fixtures, so the userFixtureLoader creates users, while your blogFixtureLoader will create blogs. Just like
we did in our fixture example above, we sometimes need some information from a fixtureloader inside another
fixtureloader. In our case: we need to have the user class inside the blogfixtureloader in order to set the owner (and
the ACL). Again, doctrine provides an easy way for this with the help of the addReference()
and getReference()
methods.
This works perfectly, but it’s not going to work with the ACL’s. The problem lies in the fact that our User objects gets
“converted” to a Doctrine User Proxy class. The getReference() in the blogFixtureLoader does not get the actual User
object back, but the proxy class. This is ok for doctrine itself, since it knows how to convert it so we still can use
it safely as our user class, but the ACL will go wrong. This is because inside the ACL tables, it will store the
classname of the actual object that needs the permission. The userSecurityIdentify::fromAccount($user)
will actually do
a get_class($user)
inside its function, which off course returns the name of the proxy class, not the name of the
actual user class. You can see this inside the “acl_security_identities” table inside your database. As soon as we check
the permissions in our blogpost controller for instance, the ACL layer will check the User that has logged in against
the ACL, but it will not find it (obviously, since we are checking a USER class against a USERPROXY class, which aren’t
the same).
Again, this is an easy fix: we override the actual class that gets saved into the ACL:
$securityIdentity = new UserSecurityIdentity($user, 'Acme\DefaultBundle\Entity\User');
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OPERATOR);
Now, we force the system to save the user as a Acme\DefaultBundle\Entity\User
class, even though it’s a Proxy class. I
haven’t checked if this is a problem with symfony2 version 2.1, but if so, it’s still an easy fix by not using the
fromAccount() method from the UserSecurityIdentity class.