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_USER
和 ROLE_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
进行关联映射,并使用 array
或 simple_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;
}
我在 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_USER
和 ROLE_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
进行关联映射,并使用 array
或 simple_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;
}