Symfony 4:ArrayCollection 添加不在数据库中持久化

Symfony 4 : ArrayCollection add not persisting in database

我在 Symfony 中有以下实体:

class User implements AdvancedUserInterface, \Serializable {
    ...
    private $roles;
    ...

    public function __construct()
    {
        ...
        $this->roles = new ArrayCollection();
        // Default role for evey user (new entity);
        $this->roles->add("ROLE_USER");
        ...
    }

    ...

    function getRoles() {
        return $this->roles->toArray();
    }

    ...

    function addRole($role){
        $this->roles->add($role);
    }

    function removeRole($role){
        $this->roles->remove($role);
    }

    ...

    public function serialize()
    {
        return serialize(array(
            ...
            $this->roles,
            ...
        ));
    }

    /** @see \Serializable::unserialize() */
    public function unserialize($serialized)
    {
        list (
            ...
            $this->roles,
            ...
        ) = unserialize($serialized, ['allowed_classes' => false]);
    }
}

当我注册一个用户时,默认角色(ROLE_USER)被正确添加。但是当我尝试编辑一个时,数据库记录没有改变:

public function UserAddRole(Request $request){
    $userId = $request->request->get("userId");
    $role = "ROLE_" . strtoupper($request->request->get("role"));

    if($role == "ROLE_USER"){
        throw $this->createNotFoundException(
            'Cannot remove ROLE_USER role'
            );
    }

    $user = $this->getDoctrine()
        ->getRepository(User::class)
        ->findOneBy(array(
            'id' => $userId
        ));

    if (!$user) {
        throw $this->createNotFoundException(
            'User not found'
            );
    }

    $user->addRole($role);
    $entityManager = $this->getDoctrine()->getManager();
    $entityManager->persist($user);
    $entityManager->flush();

    return new Response("<pre>".var_dump($user->getRoles())."</pre>");
}

此字段没有限制,目前只有硬编码值。

响应中返回的数组包含我想要的角色,但不是数据库(通过重新加载页面并直接在MySQL中检查。

有什么想法吗?

谢谢

我认为这个问题来自学说内部运作。

从这个开始:值对象应该是不可变的。记住这一点,因为它会帮助你理解学说是如何运作的。

那么发生的事情是,您使用 ROLE_USER 创建了一个新的值对象 (ArrayCollection),它被序列化并保存在数据库中。

当您获取您的实体时,您将取回您的值对象。然而,简单地向集合中添加更多项目不会改变它的 hash,这就是学说所关心的。

因此无法识别您的更改。

就价值对象而言,这种行为在学说上是普遍的。在这里阅读:https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/working-with-datetime.html

关系与集合一起工作得很好,因为它们已经准备好处理这个问题,因为它们不单独与对象哈希一起工作。 (事实上​​,自 doctrine 2.6 以来,您不应该交换集合,哈希值应该保持不变。https://github.com/doctrine/doctrine2/pull/7219

修复:使用简单数组保存角色,类型为simple_array

或:像这样创建加法器将触发保存:

public function addRole(string $role)
{
    $this->roles->add($role);
    $this->roles = clone $this->roles; // this will trigger the db update, as it creates a new object hash.
}

这里发生了一些不同的事情。根据您上面的评论,您说您正在将 $roles 映射到 array 类型。这是通过调用本机 PHP 函数 serialize(...)unserialize(...) 存储在数据库中的。这意味着如果对象具有 ROLE_USERROLE_ADMIN 的角色,数据将如下所示:

a:2:{i:0;s:9:"ROLE_USER";i:1;s:10:"ROLE_ADMIN";}

当 Doctrine 加载您的对象时,它将使用内部 PHP array 类型来存储此数据,这意味着 $this->roles 的 运行time 值为array('ROLE_USER', 'ROLE_ADMIN') 在这个例子中。

类似的类型是 simple_array,它在您的应用程序中的行为相同,但将值作为逗号分隔列表存储在数据库中。所以在这种情况下,您的数据库数据将只是:

ROLE_USER,ROLE_ADMIN

目前在您的构造函数中,您正在使用 Doctrine ArrayCollection 类型将 $roles 初始化为一个集合。但是,如果字段映射为array,从数据库中检索对象后,$roles 将是PHP array 类型,而不是ArrayCollection 对象.为了说明差异:

// the constructor is called; $roles is an ArrayCollection
$user = new User();

// the constructor is not called; $roles is an array
$user = $entityManager->getRepository(User::class)->findOneById($userId);

一般来说,实际上在我曾经 运行 遇到的每种情况下,您只想初始化为 ArrayCollection 进行关联映射,并使用 arraysimple_array 对于标量值,包括角色 属性.

您仍然可以通过使用一点点 PHP 来实现您想要的 addRole(...)removeRole(...) 行为。比如使用Doctrine注解映射:

use Doctrine\ORM\Mapping as ORM;

...

/**
 * @ORM\Column(name="roles", type="simple_array", nullable=false)
 */
private $roles;

...

/**
 * Add the given role to the set if it doesn't already exist.
 *
 * @param string $role
 */
public function addRole(string $role): void
{
    if (!in_array($role, $this->roles)) {
        $this->roles[] = $role;
    }
}

/**
 * Remove the given role from the set.
 *
 * @param string $role
 */
public function removeRole(string $role): void
{
    $this->roles = array_filter($this->roles, function ($value) use ($role) {
        return $value != $role;
    });
}

(请注意,除非您使用 PHP 7 或更高版本,否则您将无法使用类型提示)

相应地更改用户实体中的角色属性,并且不要忘记更新您的架构:

public function __construct()
{
    $this->roles = new ArrayCollection();
    $this->addRole("ROLE_USER");
}

/**
 * @var ArrayCollection
 * @ORM\Column(name="roles", type="array", nullable=true)
 */
private $roles;

/**
 * Returns the roles granted to the user.
 *
 * @return (Role|string)[] The user roles
 */
public function getRoles()
{
    return $this->roles->toArray();
}


public function addRole($role)
{
    $this->roles->add($role);
    return $this;
}

public function removeRole($role)
{
    $this->roles->removeElement($role);
    $this->roles = clone $this->roles;
}