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

安稳与你 提交于 2019-11-28 22:10:54

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!