phpsymfonydoctrine-ormfosuserbundlesymfony-2.4

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


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


Solution

  • 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.