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

时间:2018-08-11 09:37:31

标签: php orm foreign-keys fixtures symfony4

当前,我正在尝试修改类,并寻找节省用户和角色之间动态关系的想法。

我想在加载夹具时创建关联,并且还需要在需要创建具有关系的用户时在控制器中具有这种功能,例如:

...
$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();

我的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文件:

    <?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;
        }
    }

我的数据固定装置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();
        }
    }

我正在寻找建议:

  1. 用户实体被缓存在注释中,因为每个请求上的symfony都将其加载。配置部分:

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

我正在Redis中缓存所有数据1分钟。有更好的解决方案吗?

  1. DB模式在users_roles上创建外键(FK)和额外索引 桌子,我很想不要FK,因为恕我直言,这是“沉重的”事情。我希望仅在(user_id,role_id)上具有主键。有什么建议吗?

  2. 为用户+角色+ Roles_users灵活添加/删除的解决方案

非常感谢!

1 个答案:

答案 0 :(得分:0)

我会尽力回答这个问题,但是正如我在评论中提到的那样,这个问题有点大,所以其中一些可能会被普遍化。

  
      
  1. 用户实体被缓存在注释中,因为在每个请求上使用symfony   加载它。配置部分:
  2.   
orm:
    metadata_cache_driver:
      type: redis
    result_cache_driver:
      type: redis
    query_cache_driver:
      type: redis
     

我正在Redis中缓存所有数据1分钟。有更好的解决方案吗?

在这里说“更好”时,这是主观的。每个应用程序都是不同的。缓存以及缓存保持活动的时间长短取决于每个应用程序的要求。我不知道这是否本质上是错误,但这很大程度上取决于您的应用程序要求。

  
      
  1. 数据库架构在users_roles表上创建外键(FK)和其他索引,我希望不要使用FK,因为恕我直言   这是“沉重的”事情。我希望在主键上   (user_id,role_id)仅。有什么建议吗?
  2.   

首先,FK非常重要,它们旨在保持数据的完整性。它们确保如果有人尝试删除与user_roles行链接的用户或角色,则该操作将失败。通常,您希望这样做是为了避免丢失数据或创建孤立数据。

第二,我不确定您使用的是哪个版本的Doctrine,但是我的类似ManyToMany表(user_id, role_id)上创建了PK和唯一索引。

  
      
  1. 为用户+角色+ Roles_users灵活添加/删除的解决方案
  2.   

您可以使用Doctrine的ArrayCollections(在菜单中单击“收藏”以确保锚点链接有效,有时它们会断开)来实现此目的。

不过,使用默认的Symfony User实体进行此操作时有一个警告。它实现了Symfony\Component\Security\Core\User\UserInterface接口,该接口定义了getRoles()方法,该方法用于将角色数组作为字符串返回。这样,某些Symfony安全功能将按预期工作。这意味着,如果您拥有private $roles属性,则必须将其标准Doctrine getter重命名为其他名称,以便可以使getRoles()正常运行。

因此,对于User实体,我通常只是将自己的getter,setter,adder和remover重命名为getUserRoles()setUserRoles()addUserRole()和{ {1}},然后离开removeUserRole()来实现预期的接口。

这是用户类的不完整示例,没有Role类示例。

getRoles()

如果需要,您也可以对<?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); } /* ... */ } 对象执行相反的操作,但这取决于是否要以这种方式将用户添加到角色中,并且通常最好选择关系的所有者。 / p>

这里是一个示例,说明如何在您使用实体,固定装置或其他方式的任何地方使用它。

Role