FOSUserBundle Override Roles - Property "roles" in "Acme\DemoBundle\Entity\User" was already declared, but it must be declared only once
Asked Answered
W

1

3

I like most people are trying to override the FOSUserBundle roles so I can map them ManyToMany to a Role Entity.

Unfortunately for some reason due to the mapping of the Model/User I get the following:

Property "roles" in "Acme\DemoBundle\Entity\User" was already declared, but it must be declared only once

There seems to be some workaround mentioned in this git issue posted in FOSUserBundle:

https://github.com/FriendsOfSymfony/FOSUserBundle/pull/1081#issuecomment-19027818

I am Doctrine ORM and using Annotations for mapping not yml or xml. Latest Symfony (2.4) and latest FOSUB.

I tried the alternative option by copying everything into my Entity and not extending, but to be honest that messed up everything.

I am trying to attempt the idea of creating my own Model/User extending FOSUserBundle/Model/User with no mappings. And then extend my Entity/User from this. I tried but I still got the same issue. I'm assuming I did this incorrectly.

Can someone advise/show how I would do this correctly?

I really need to be able to override roles as although the FOSUserBundle is great, the adaptation of roles isn't very good. Although I appreciate at the time this was the only way they could do it and changing it now breaks BC.

Hope someone can help.

Kind regards Paul Pounder

Willetta answered 28/3, 2014 at 16:45 Comment(7)
Why not using the groups feature of FOSUserBundle?Agreed
Do not use the User Bundle provided by FOS. It's gonna be one hell of a ride. Better build your custom user provider. Easy to achieve.Coracle
I'm already using Groups as part of my application to store users in companies and apply roles against them. I still need to be able to apply roles against specific users, and not group them. @cept0 - I agree, but it's perfect for what I need it for (except the roles) :)Willetta
It might be that I end up using Groups, and change the way I store companies, but thought I would investigate this first. Plus FOSUserBundle integrates well with the other bundles I'm using so don't want to break that link now.Willetta
@PaulPounder Regarding to your comment on the well integrity of the FOS User Bundle: There is no special magic FOS User Bundle integration. The bundle integrates itself into Symfony and other bundles may use this "bridge".Coracle
That's pretty much what I meant :-)Willetta
I had a struggle solving this with FOS but in the end it's not much work, only a few tweaks, just much reading... :)Extinguish
E
16

I had the same Issue, using Annotations also.

Note: As some readers had issues putting all together I created a gitHub repo with my UserBundle. If you find that there's something missing in this HowTo, let me know so I add it.

This post covers three aspects, DB based Roles with Tree structure implementation, framework configuration to also support RoleHierarchy (getReachableRoles) for DB roles. Without which it'd be useless to have roles in DB after all. And Doctrine Subscribers to create Roles upon certain Entity being persisted.

The changes FOS had to make were deep, and well documented, but I must say that a single HowTo Use sample code would have prevented me from reading a lot, (not complaining, at least I know a little on compiler passes now.)

Roles to DB

I'm using Sf 2.4, but this should work since 2.3. Here is my solution's involved files, consider one step per file:

./:
composer.json

src/Application/UsuarioBundle/:
ApplicationUsuarioBundle.php

src/Application/UsuarioBundle/Resources/config/doctrine/model/:
User.orm.xml

src/Application/UsuarioBundle/Entity/:
Role.php  Usuario.php

In the copmoser.json I upgraded doctrine-bundle so it includes required files:

"require": {
...
    "doctrine/doctrine-bundle": "~1.3@dev",
...
}

In the Bundle.php file you must register a compiler pass

namespace Application\UsuarioBundle;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass;

class ApplicationUsuarioBundle extends Bundle
{

    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $mappings = array(
            realpath(__DIR__ . '/Resources/config/doctrine/model') => 'FOS\UserBundle\Model',
            realpath(__DIR__ . '/Resources/config/doctrine/model') => 'FOS\UserBundle\Entity', 
        );

        $container->addCompilerPass(
            DoctrineOrmMappingsPass::createXmlMappingDriver(
                $mappings, array('fos_user.model_manager_name'), false
            )
        );
}

This is the dependency imported by the new version of doctrine-bundle:

`\Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass`. 

I assume that this mapping info is added later than FOSUSerBundle's because I just repeated the process (simplified for only ORM) I saw in FOSUerBundle.php hoping it'd take precedence and it did.

The mappings in User.orm.xml are the exact copy of ./vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/doctrine/model/User.orm.xml with line #35 commented out. This deletes the conflicting mapping on roles in the mapped super class.

<!--<field name="roles" column="roles" type="array" />-->

From now on, you just do what you wanted to do in 1st place, implement your idea of Roles. Here's mine: The User Class that extends FOS\UserBundle\Model\User, but now with the mappings you used in your bundle.

src/Application/UsuarioBundle/Entity/Role.php

And the Role Class:

src/Application/UsuarioBundle/Entity/Usuario.php

After doing it you- can see that the right SQL changes are dumped by schema update --dump-sql.

$ php app/console doctrine:schema:update --dump-sql --complete
CREATE TABLE fos_usuario_role (usuario_id INT NOT NULL, role_id INT NOT NULL, INDEX IDX_6DEF6B87DB38439E (usuario_id), INDEX IDX_6DEF6B87D60322AC (role_id), PRIMARY KEY(usuario_id, role_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
CREATE TABLE fos_role (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(30) NOT NULL, role VARCHAR(20) NOT NULL, UNIQUE INDEX UNIQ_4F80385A57698A6A (role), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
ALTER TABLE fos_usuario_role ADD CONSTRAINT FK_6DEF6B87DB38439E FOREIGN KEY (usuario_id) REFERENCES fos_user (id) ON DELETE CASCADE;
ALTER TABLE fos_usuario_role ADD CONSTRAINT FK_6DEF6B87D60322AC FOREIGN KEY (role_id) REFERENCES fos_role (id) ON DELETE CASCADE;
ALTER TABLE fos_user DROP roles;

Still, I'm not representing Role hierarchies, which I need.

Hope it's useful for someone. I'm sure you already solved this, or lost your job :p.

Documentation I followed:

https://github.com/symfony/symfony/pull/7599
https://github.com/FriendsOfSymfony/FOSUserBundle/pull/1081
http://symfony.com/doc/2.4/cookbook/doctrine/mapping_model_classes.html
http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html

RoleHierarchy Implementation

Files involved in the Solution:

// The Magician, for I just re-instantiated RoleHierarchyVoter & ExpressionVoter 
// classes as ApplicationUsuarioBundle services; passing my RoleHierarchy 
// implementation. 
src/Application/UsuarioBundle/Role/RoleHierarchy.php

// duplicating security.access.expression_voter && 
// application_usuario.access.role_hierarchy_voter BUT WITH NEW 
// RoleHierarchy ARGUMENT
src/Application/UsuarioBundle/Resources/config/services.xml

// Entities, important methods are collection related
src/Application/UsuarioBundle/Entity/Role.php
src/Application/UsuarioBundle/Entity/Usuario.php

// Edited, commented out regular hardcoded roleHierarchy
app/config/security.yml

// CRUD related, sample files will add dependencies to lexik/form-filter-bundle; 
// docdigital/filter-type-guesser; white-october/pagerfanta-bundle
src/Application/UsuarioBundle/Controller/RoleController.php
src/Application/UsuarioBundle/Form/RoleType.php
src/Application/UsuarioBundle/Resources/views/Role/edit.html.twig
src/Application/UsuarioBundle/Resources/views/Role/index.html.twig
src/Application/UsuarioBundle/Resources/views/Role/new.html.twig
src/Application/UsuarioBundle/Resources/views/Role/show.html.twig

you can see files in this gist

Or access each file direcctly, (as Gist doesn't preserve the listing order).

src/Application/UsuarioBundle/Role/RoleHierarchy.php

src/Application/UsuarioBundle/Resources/config/services.xml

src/Application/UsuarioBundle/Entity/Role.php

src/Application/UsuarioBundle/Entity/Usuario.php

app/config/security.yml

src/Application/UsuarioBundle/Controller/RoleController.php

src/Application/UsuarioBundle/Form/RoleType.php

src/Application/UsuarioBundle/Resources/views/Role/edit.html.twig

Documentation followed:

cookbook: security voters

Components: Security

reference: Service tags priorities

Souce: RoleHierarchyInterface

Doctrine Subscribers

You'll get this far to realize there's something missing...

The main reason I ported Roles to DB is because I'm dealing with a dynamic (from a Structural perspective) Application that enables the user to configure a workflow. When I add a new Area, with a new Process, with a new Activity (or update either the name or the parent-Child relationship, or remove any), I need new Roles to be generated automatically.

Then you think of the Doctrine Subscribers for LyfeCycleEvents, but adding new entities in the PrePersist/PreUpdate will demand a nested flush, which in my case messes things up, its easier when you just need to update some fields on already "computedChanges" entities.

So what I used to hook and create/edit/delete roles is the onFlush, at which point the computChangeSet() works fine for adding new entities..

I'll leave the ProcessRolesSubscriber Gist as an example.

Extinguish answered 7/7, 2014 at 14:22 Comment(8)
Well I appreciate your perseverence with StackOverflow editor. That is a great solution. I hadn't solved it to be honest, and thankfully still in a job :-)Willetta
thanks, I'm glad you'r still in your job :). I'm implementing RoleHierarchyVoter for this. I'll add it to the answer when complete.Extinguish
thanks, very helpful post. But there is a problem. When you want to remove $roles from User completely, for example, if you store it in separate database, because EntityManager loads metadata from all parents classes, so in result it will be fail with exception that there is no such column 'roles'.Ratchford
Thanks. You must implement user provider interface, hjust make sure you implement the getRoles() method and rrmove the roles field from user. Then update your schema. Sorry I can't consult my code. I'm posting through my cell phone.Extinguish
I followed exact steps but getting this exception [Doctrine\ORM\Mapping\MappingException] Property "roles" in "St\UserBundle\Entity\User" was already declared, but it must be declared only onceBrophy
Weird, have you commented out the roles xml mappings? If so, you coul play arround with the order in which bundles are loaded un appKernel.php. checkout the git Repo, with the entire USerBndle.Extinguish
Taking ideas from here and there, I've built my own UserBundle repo to handle hierarchical Roles attached to Groups, and Users on them without direct relation to the Roles to manage them in batch. The Admin Panel is still pending, feedback and pull requests are welcome :D github.com/matheo/UserBundleDeci
I needed to replace the FOS\UserBundle\Entity by the path to the folder containing my user entity to make it work but then yes, thanks !! it works !Chesterton

© 2022 - 2024 — McMap. All rights reserved.