简体   繁体   English

Symfony 4 - 为动态用户角色加载装置

[英]Symfony 4 - load fixtures for dynamic user roles

Currently I'm trying to modify my classes and looking for idea to save dynamic relations between users and roles.目前我正在尝试修改我的类并寻找保存用户和角色之间动态关系的想法。

I want to create associations when loading fixtures and also to have such a functionality in controller when I need to create an user with relation, example:我想在加载装置时创建关联,并在需要创建具有关系的用户时在控制器中具有这样的功能,例如:

...
$user = new User();
$user->setName($_POST['name']);
$user->setPassword($_POST['password']);
...
$user->setRole('ROLE_USER');//Role for everyone
...
$role = new Role();
$role->setName('ROLE_' . strtoupper($_POST['name']) );//Role for personal use
...
//Here need to implement user+role association (I'm looking for recommendations)
...
$entityManager->persist($user);
$entityManager->persist($role);
//Persist role+user assosiacion
$entityManager->flush();
$entityManager->clear();

My User.php :我的 User.php :

    <?php

    namespace App\Entity;

    use DateTime;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
    use Symfony\Component\Security\Core\User\UserInterface;

    /**
     * User
     *
     * @ORM\Table(name="user", uniqueConstraints={@ORM\UniqueConstraint(name="user_name", columns={"user_name"}), @ORM\UniqueConstraint(name="email", columns={"email"})})
     * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
     * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="fast_cache")
     * @UniqueEntity(fields="email", message="Email already taken")
     * @UniqueEntity(fields="username", message="Username already taken")
     */
    class User implements UserInterface, \Serializable
    {
        /**
         * @var ArrayCollection
         *
         * @ORM\ManyToMany(targetEntity="App\Entity\Role", inversedBy="users", cascade={"remove"})
         * @ORM\JoinTable(name="users_roles",
         *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
         *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
         *      )
         */
        protected $roles;
        /**
         * @var int
         *
         * @ORM\Column(name="id", type="smallint", nullable=false, options={"unsigned"=true})
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
        /**
         * @var string
         *
         * @ORM\Column(name="user_name", type="string", length=255, nullable=false)
         */
        private $username;
        /**
         * @var string
         *
         * @ORM\Column(name="email", type="string", length=255, nullable=false)
         */
        private $email;
        /**
         * @var string
         *
         * @ORM\Column(name="password", type="string", length=255, nullable=false)
         */
        private $password;
        /**
         * @var bool
         *
         * @ORM\Column(name="is_enabled", type="boolean", nullable=false)
         */
        private $isEnabled;
        /**
         * @var bool
         *
         * @ORM\Column(name="is_verified", type="boolean", nullable=false)
         */
        private $isVerified;
        /**
         * @var DateTime
         *
         * @ORM\Column(name="created_at", type="datetime", nullable=false)
         */
        private $createdAt;
        /**
         * @var DateTime
         *
         * @ORM\Column(name="updated_at", type="datetime", nullable=false)
         */
        private $updatedAt;

        /**
         * @return int
         */
        public function getId(): int
        {
            return $this->id;
        }

        /**
         * @return string
         */
        public function getEmail(): string
        {
            return $this->email;
        }

        /**
         * @param string $email
         */
        public function setEmail(string $email): void
        {
            $this->email = $email;
        }

        /**
         * @return bool
         */
        public function isEnabled(): bool
        {
            return $this->isEnabled;
        }

        /**
         * @param bool $isEnabled
         */
        public function setIsEnabled(bool $isEnabled): void
        {
            $this->isEnabled = $isEnabled;
        }

        /**
         * @return bool
         */
        public function isVerified(): bool
        {
            return $this->isVerified;
        }

        /**
         * @param bool $isVerified
         */
        public function setIsVerified(bool $isVerified): void
        {
            $this->isVerified = $isVerified;
        }

        /**
         * @return DateTime
         */
        public function getCreatedAt(): DateTime
        {
            return $this->createdAt;
        }

        /**
         * @param DateTime $createdAt
         */
        public function setCreatedAt(DateTime $createdAt): void
        {
            $this->createdAt = $createdAt;
        }

        /**
         * @return DateTime
         */
        public function getUpdatedAt(): DateTime
        {
            return $this->updatedAt;
        }

        /**
         * @param DateTime $updatedAt
         */
        public function setUpdatedAt(DateTime $updatedAt): void
        {
            $this->updatedAt = $updatedAt;
        }

        /**
         * String representation of object
         * @link http://php.net/manual/en/serializable.serialize.php
         * @return string the string representation of the object or null
         * @since 5.1.0
         * NOTE: SYNFONY BUG 3.4 -> 4.1; https://github.com/symfony/symfony-docs/pull/9914
         */
        public function serialize(): string
        {
            // add $this->salt too if you don't use Bcrypt or Argon2i
            return serialize([$this->id, $this->username, $this->password]);
        }

        /**
         * Constructs the object
         * @link http://php.net/manual/en/serializable.unserialize.php
         * @param string $serialized <p>
         * The string representation of the object.
         * </p>
         * @return void
         * @since 5.1.0
         */
        public function unserialize($serialized): void
        {
            // add $this->salt too if you don't use Bcrypt or Argon2i
            [$this->id, $this->username, $this->password] = unserialize($serialized, ['allowed_classes' => false]);
        }

        /**
         * Returns the roles granted to the user.
         *
         * <code>
         * public function getRoles()
         * {
         *     return array('ROLE_USER');
         * }
         * </code>
         *
         * Alternatively, the roles might be stored on a ``roles`` property,
         * and populated in any number of different ways when the user object
         * is created.
         *
         * @return array The user roles
         */
        public function getRoles(): array
        {
            $roles = [];
            foreach ($this->roles->toArray() AS $role) {
                $roles[] = $role->getName();
            }
            return $roles;
        }

        /**
         * Returns the password used to authenticate the user.
         *
         * This should be the encoded password. On authentication, a plain-text
         * password will be salted, encoded, and then compared to this value.
         *
         * @return string The password
         */
        public function getPassword(): string
        {
            return $this->password;
        }

        /**
         * @param string $password
         */
        public function setPassword(string $password): void
        {
            $this->password = $password;
        }

        /**
         * Returns the salt that was originally used to encode the password.
         *
         * This can return null if the password was not encoded using a salt.
         *
         * @return string|null The salt
         */
        public function getSalt()
        {
            // See "Do you need to use a Salt?" at https://symfony.com/doc/current/cookbook/security/entity_provider.html
            // we're using bcrypt in security.yml to encode the password, so
            // the salt value is built-in and you don't have to generate one

            return null;
        }

        /**
         * Returns the username used to authenticate the user.
         *
         * @return string The username
         */
        public function getUsername()
        {
            return $this->username;
        }

        /**
         * @param string $username
         */
        public function setUsername(string $username): void
        {
            $this->username = $username;
        }

        /**
         * Removes sensitive data from the user.
         *
         * This is important if, at any given point, sensitive information like
         * the plain-text password is stored on this object.
         */
        public function eraseCredentials()
        {
            // if you had a plainPassword property, you'd nullify it here
            $this->plainPassword = null;
        }
    }

Role.php file:角色.php文件:

    <?php

    namespace App\Entity;

    use DateTime;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;

    /**
     * Role
     *
     * @ORM\Table(name="role", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"})})
     * @ORM\Entity(repositoryClass="App\Repository\RoleRepository")
     */
    class Role
    {
        /**
         * @var ArrayCollection
         *
         * @ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="roles", cascade={"remove"})
         * @ORM\JoinTable(name="users_roles",
         *      joinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")},
         *      inverseJoinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}
         *      )
         */
        protected $users;
        /**
         * @var int
         *
         * @ORM\Column(name="id", type="smallint", nullable=false, options={"unsigned"=true})
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
        /**
         * @var string
         *
         * @ORM\Column(name="name", type="string", length=255, nullable=false)
         */
        private $name;
        /**
         * @var DateTime
         *
         * @ORM\Column(name="created_at", type="datetime", nullable=false)
         */
        private $createdAt;
        /**
         * @var DateTime
         *
         * @ORM\Column(name="updated_at", type="datetime", nullable=false)
         */
        private $updatedAt;

        /**
         * Role constructor.
         */
        public function __construct()
        {
            $this->users = new ArrayCollection();
        }

        /**
         * @return array
         */
        public function getUsers(): array
        {
            return $this->users->toArray();
        }

        /**
         * @return int
         */
        public function getId(): int
        {
            return $this->id;
        }

        /**
         * @param int $id
         */
        public function setId(int $id): void
        {
            $this->id = $id;
        }

        /**
         * @return string
         */
        public function getName(): string
        {
            return $this->name;
        }

        /**
         * @param string $name
         */
        public function setName(string $name): void
        {
            $this->name = $name;
        }

        /**
         * @return DateTime
         */
        public function getCreatedAt(): DateTime
        {
            return $this->createdAt;
        }

        /**
         * @param DateTime $createdAt
         */
        public function setCreatedAt(DateTime $createdAt): void
        {
            $this->createdAt = $createdAt;
        }

        /**
         * @return DateTime
         */
        public function getUpdatedAt(): DateTime
        {
            return $this->updatedAt;
        }

        /**
         * @param DateTime $updatedAt
         */
        public function setUpdatedAt(DateTime $updatedAt): void
        {
            $this->updatedAt = $updatedAt;
        }
    }

My data fixtures AppFixtures.php:我的数据装置 AppFixtures.php:

    <?php

    namespace App\DataFixtures;

    use App\Entity\Role;
    use App\Entity\User;
    use DateTime;
    use Doctrine\Bundle\FixturesBundle\Fixture;
    use Doctrine\Common\Persistence\ObjectManager;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

    /**
     * Class AppFixtures
     * @package App\DataFixtures
     */
    class AppFixtures extends Fixture
    {
        /**
         * @var UserPasswordEncoderInterface
         */
        private $encoder;
        /**
         * @var EntityManagerInterface
         */
        private $entityManager;

        /**
         * AppFixtures constructor.
         * @param UserPasswordEncoderInterface $userPasswordEncoder
         * @param EntityManagerInterface $entityManager
         */
        public function __construct(UserPasswordEncoderInterface $userPasswordEncoder, EntityManagerInterface $entityManager)
        {
            $this->encoder = $userPasswordEncoder;
            $this->entityManager = $entityManager;
        }

        /**
         * @param ObjectManager $manager
         */
        public function load(ObjectManager $manager)
        {
            //Creating default roles
            $role = new Role();
            $role->setName('ROLE_USER');
            $role->setCreatedAt(new DateTime());
            $role->setUpdatedAt(new DateTime());
            $manager->persist($role);
            $role = new Role();
            $role->setName('ROLE_MODERATOR');
            $role->setCreatedAt(new DateTime());
            $role->setUpdatedAt(new DateTime());
            $manager->persist($role);
            $role = new Role();
            $role->setName('ROLE_ADMIN');
            $role->setCreatedAt(new DateTime());
            $role->setUpdatedAt(new DateTime());
            $manager->persist($role);
            $manager->flush();
            $manager->clear();
            //Creating users
            $user = new User();
            $user->setUserName('john');
            $user->setEmail('john@localhost');
            //$user->setRoles(['ROLE_USER', 'ROLE_JOHN']);
            //$roleAssociation = null;
            $user->setPassword($this->encoder->encodePassword($user, 'test'));
            $user->setCreatedAt(new DateTime());
            $user->setUpdatedAt(new DateTime());
            $user->setIsVerified(true);
            $user->setIsEnabled(true);
            //$manager->persist($roleAssociation);
            $manager->persist($user);

            $user = new User();
            $user->setUserName('tom');
            $user->setEmail('tom@localhost');
            //$user->setRoles(['ROLE_USER', 'ROLE_TOM', 'ROLE_MODERATOR']);
            //$roleAssociation = null;
            $user->setPassword($this->encoder->encodePassword($user, 'test'));
            $user->setCreatedAt(new DateTime());
            $user->setUpdatedAt(new DateTime());
            $user->setIsVerified(true);
            $user->setIsEnabled(true);
            //$manager->persist($roleAssociation);
            $manager->persist($user);

            $user = new User();
            $user->setUserName('jimmy');
            $user->setEmail('jimmy@localhost');
            //$user->setRoles(['ROLE_USER', 'ROLE_JIMMY', 'ROLE_ADMIN']);
            //$roleAssociation = null;
            $user->setPassword($this->encoder->encodePassword($user, 'test'));
            $user->setCreatedAt(new DateTime());
            $user->setUpdatedAt(new DateTime());
            $user->setIsVerified(true);
            $user->setIsEnabled(true);
            //$manager->persist($roleAssociation);
            $manager->persist($user);

            $manager->flush();
            $manager->clear();
        }
    }

I'm looking for advises for:我正在寻找以下方面的建议:

  1. User entity is cached in annotation, because symfony on each request loads it.用户实体缓存在注解中,因为每个请求的 symfony 都会加载它。 config part:配置部分:

     orm: metadata_cache_driver: type: redis result_cache_driver: type: redis query_cache_driver: type: redis

I'm caching all the data for 1min in redis.我在 redis 中缓存所有数据 1 分钟。 Any better solution?有什么更好的解决办法吗?

  1. DB schema creates Foreign Keys (FK) and extra indexes on users_roles table and I would love to not to have FK, because IMHO it's "heavy" thing. DB 模式在 users_roles 表上创建外键 (FK) 和额外的索引,我希望没有 FK,因为恕我直言,这是“重”的事情。 I would prefer to have primary key on (user_id,role_id) only.我宁愿只在 (user_id,role_id) 上使用主键。 Any recommendations of this?这方面有什么建议吗?

  2. The solution for having flexible add/remove for user + role + roles_users user+role+roles_users灵活增删解决方案

Big thanks!十分感谢!

I'll try to answer this but as I mentioned in my comment, this question is a bit large so some of this may be generalized.我会尝试回答这个问题,但正如我在评论中提到的,这个问题有点大,所以其中一些可能是概括的。

  1. User entity is cached in annotation, because symfony on each request loads it.用户实体缓存在注解中,因为每个请求的 symfony 都会加载它。 config part:配置部分:
 orm: metadata_cache_driver: type: redis result_cache_driver: type: redis query_cache_driver: type: redis

I'm caching all the data for 1min in redis.我在 redis 中缓存所有数据 1 分钟。 Any better solution?有什么更好的解决办法吗?

When you say "better" here, it's subjective.当你在这里说“更好”时,这是主观的。 Each application is different.每个应用程序都是不同的。 Caches, and the length at which caches stay alive, is specific to each application's requirements.缓存以及缓存保持活动的长度特定于每个应用程序的要求。 I don't know if there's anything inherently wrong with this, but it's largely dependent on your app's requirements.我不知道这是否有任何本质上的错误,但这在很大程度上取决于您的应用程序的要求。

  1. DB schema creates Foreign Keys (FK) and extra indexes on users_roles table and I would love to not to have FK, because IMHO it's "heavy" thing. DB 模式在 users_roles 表上创建外键 (FK) 和额外的索引,我希望没有 FK,因为恕我直言,这是“重”的事情。 I would prefer to have primary key on (user_id,role_id) only.我宁愿只在 (user_id,role_id) 上使用主键。 Any recommendations of this?这方面有什么建议吗?

First, FKs are important, and they're intended to keep integrity in your data.首先,FK 很重要,它们旨在保持数据的完整性。 They ensure that if someone attempted to delete a user or role that a user_roles row linked to, the operation would fail.它们确保如果有人试图删除user_roles行链接到的用户或角色,操作将失败。 You usually want this behavior so that you don't lose data or create orphaned data.您通常希望这种行为不会丢失数据或创建孤立数据。

Secondly, I'm not sure what version of Doctrine you're using but my similar ManyToMany tables do create a PK and unique index on (user_id, role_id) .其次,我不确定您使用的是什么版本的 Doctrine,但我类似的 ManyToMany 表确实(user_id, role_id)上创建了 PK 和唯一索引。

  1. The solution for having flexible add/remove for user + role + roles_users user+role+roles_users灵活增删解决方案

You do this by using Doctrine's ArrayCollections (click Collections in the menu to make sure the anchor link worked, they're broken sometimes).您可以通过使用 Doctrine 的ArrayCollections (单击菜单中的 Collections 以确保锚链接有效,它们有时会损坏)来完成此操作。

There is one caveat when doing this with the the default Symfony User entity though.但是,在使用默认的 Symfony User实体执行此操作时有一个警告。 It implements the Symfony\\Component\\Security\\Core\\User\\UserInterface interface which defines a getRoles() method that is meant to return an array of roles as strings.它实现了Symfony\\Component\\Security\\Core\\User\\UserInterface接口,该接口定义了一个getRoles()方法,该方法旨在将角色数组作为字符串返回。 This is so that certain Symfony security features work as expected.这是为了使某些 Symfony 安全功能按预期工作。 This means that if you have a private $roles property that you will have to rename its standard Doctrine getter to something else so that you can leave getRoles() functioning as expected.这意味着,如果您有一个private $roles属性,您必须将其标准 Doctrine getter 重命名为其他名称,以便您可以让getRoles()按预期运行。

So, for the User entity, I typically just rename my getter, setter, adder, and remover to something like getUserRoles() , setUserRoles() , addUserRole() , and removeUserRole() and then I leave getRoles() to implement the expected interface.因此,对于User实体,我通常只是将我的 getter、setter、adder 和 remover 重命名为getUserRoles()setUserRoles()addUserRole()removeUserRole() ,然后我离开getRoles()来实现预期的界面。

Here is an incomplete example of a user class with no Role class example.这是一个没有 Role 类示例的用户类的不完整示例。

<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection/*Interface*/;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use \InvalidArgumentException;
use function array_unique;

class User implements UserInterface
{
    /* ... */

    /**
     * @var Role[]|array User's roles
     *
     * Note that the getter, setter, adder, and remover for this method are renamed so that the getRoles() method that
     * we implement from Symfony\Component\Security\Core\User\UserInterface can function as expected.
     *
     * @see UserInterface
     * @see User::getRoles()
     * @see User::getUserRoles()
     * @see User::setUserRoles()
     * @see User::addUserRole()
     * @see User::removeUserRole()
     *
     * @ORM\ManyToMany(targetEntity="App\Entity\Role", inversedBy="users", cascade={"persist"})
     */
    private $roles;

    /* ... */

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->roles = new ArrayCollection();
    }

    /* ... */


    /**
     * @return Collection|Role[]
     */
    public function getUserRoles(): Collection
    {
        return $this->roles;
    }

    /**
     * @param Collection|Role[] $roles
     *
     * @return self
     */
    public function setUserRoles(Collection/*Interface*/ $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @param Role $role
     *
     * @return self
     */
    public function addUserRole(Role $role): self
    {
        $this->roles->add($role);

        return $this;
    }

    /**
     * @param Role $role
     *
     * @return self
     */
    public function removeUserRole(Role $role): self
    {
        $this->roles->removeElement($role);

        return $this;
    }

    /**
     * Get array of roles as strings
     *
     * This method is an implementation of UserInterface::getRoles(). The getter for self::$roles is
     * self::getUserRoles().
     *
     * @return string[]|array
     *
     * @see UserInterface
     */
    public function getRoles()
    {
        $roleStrings = [];

        foreach ($this->roles as $role) {
            $roleStrings[] = $role->getName();
        }

        // guarantee every user at least has ROLE_USER
        $roleStrings[] = 'ROLE_USER';

        return array_unique($roleStrings);
    }

    /* ... */
}

You can also do the reverse for the Role object if you wanted to, but this depends on if you wanted to add users to roles that way and it's often best to choose the owning side of the relation.如果您愿意,您也可以对Role对象执行相反的操作,但这取决于您是否希望以这种方式将用户添加到角色,并且通常最好选择关系的拥有方。

Here is an example of how this can be used anywhere where you're working with entities, fixtures or otherwise.下面是一个示例,说明如何在您使用实体、装置或其他方式的任何地方使用它。

<?php

use Doctrine\Common\Collections\ArrayCollection;
use App\Entity\User;
use App\Entity\Role;

$entityManager = (get entity manager);

$user = new User();

// You can also add the name as an argument to Role::__construct()
// then call from setName() from that if you wanted to simplify this.
$roleFoo = (new Role())->setName('ROLE_FOO');
$roleBar = (new Role())->setName('ROLE_BAR');

// set roles using ArrayCollection of roles.
$user->setUserRoles(new ArrayCollection($roleFoo, $roleBar));

// add new role, see User::addUserRole() for 
$user->addUserRole((new Role()->setName('ROLE_WIN'));

// remove ROLE_BAR
// You can also do this with entities that you find with Doctrine
// if you want to remove them from a persisted collection.
$user->removeUserRole($roleBar);

// get roles as a collection of Role objects.
// This will return either ArrayCollection or PersistentCollection
// depending on context. These are objects that act like arrays
// and each element will be a Role object.
$roles = $user->getUserRoles();

// get roles as strings... mostly used by Symfony's security system
// but could be used by your app too.
$roleStrings = $user->getRoles();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM