我在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中进行检查。
知道吗?
感谢
这里发生了一些不同的事情。根据您上面的评论,您说过您正在将$roles
映射到array
类型。这是通过调用本地PHP函数serialize(...)
和unserialize(...)
存储在数据库中的。这意味着,如果一个对象具有ROLE_USER
和ROLE_ADMIN
的角色,那么数据将如下所示:
a:2:{i:0;s:9:"ROLE_USER";i:1;s:10:"ROLE_ADMIN";}
当Doctrine加载对象时,它将使用内部PHParray
类型来存储这些数据,这意味着在本例中$this->roles
的运行时值为array('ROLE_USER', 'ROLE_ADMIN')
。
类似的类型是simple_array
,它在应用程序中的行为相同,但在数据库中以逗号分隔的列表形式存储值。因此,在这种情况下,您的数据库数据将是:
ROLE_USER,ROLE_ADMIN
当前在构造函数中,您正在使用DoctrineArrayCollection
类型将$roles
初始化为集合。但是,如果字段映射为array
,则从数据库检索对象后,$roles
将是PHParray
类型,而不是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
,并为标量值(包括roles属性(使用array
或simple_array
。
您仍然可以通过使用一点PHP来实现您想要的addRole(...)
和removeRole(...)
行为。例如,使用条令注释映射:
use DoctrineORMMapping as ORM;
...
/**
* @ORMColumn(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;
});
}
(注意,除非您使用的是PHP7或更高版本,否则您将无法使用类型提示(
我认为这个问题来自条令的内部运作。
从以下内容开始:Value对象应该是不可变的。记住这一点,因为它将帮助你理解教义是如何运作的。
因此,您将使用ROLE_USER创建一个新的值对象(ArrayCollection(,该对象将被序列化并保存在数据库中。
当您获取实体时,您将获取值对象。然而,简单地向集合中添加更多的项目并不会改变它的散列,这正是学说所关心的。
因此,您的更改不会被识别。
就价值对象而言,这种行为在学说中是普遍的。请在此处阅读:https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/working-with-datetime.html
关系可以很好地处理集合,因为它们已经准备好处理这个问题,因为它们不能单独处理对象哈希。(事实上,由于原则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.
}
相应地更改用户实体中的角色属性,不要忘记更新您的模式:
public function __construct()
{
$this->roles = new ArrayCollection();
$this->addRole("ROLE_USER");
}
/**
* @var ArrayCollection
* @ORMColumn(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;
}