我们有一项服务,其目的是将用户操作记录到数据库中。底层实体 ActionLog 与 User 实体具有 manyToOne 关系。这两个实体都绑定到相同的DBAL连接(和ORM EntityManager)。
问题:当一个新的 ActionLog 实体被持久化并刷新时会引发异常,说我们应该将对象集级联为 #user < / em>属性因为被认为是新的:
通过“Doctrine \ Model \ ActionLog#user”关系找到了一个新实体,该关系未配置为级联实体的持久操作:John Doe。要解决此问题:在此未知实体上显式调用EntityManager#persist()或在映射中配置级联持久保存此关联,例如@ManyToOne(..,cascade = {“persist”})。
这很烦人,因为User实例实际上直接来自数据库,因此根本不是新的!我们希望 entityManager 已经将此User对象设为"MANAGED"并且为referenced through the identity map(换句话说,该对象不是“分离的”)。
那么,为什么Doctrine会将User实体实例(经过身份验证的用户)视为已分离/新?
使用Symfony 4.0.6; doctrine / orm v2.6.1,doctrine / dbal 2.6.3,doctrine / doctrine-bundle 1.8.1
ActionLog 模型映射提取
Doctrine\Model\ActionLog:
type: entity
table: action_log
repositoryClass: Doctrine\Repository\ActionLogRepository
manyToOne:
user:
targetEntity: Doctrine\Model\User
id: # …
fields: # …
记录服务声明
log_manager:
class: Service\Log\LogManager
public: true
arguments:
- "@?security.token_storage"
calls:
# setter required instead of the dependency injection
# to prevent circular dependency.
- ['setEntityManager', ["@doctrine.orm.entity_manager"]]
日志服务实现 - 创建新的ActionLog记录
<?php
namespace Service\Log;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Model\User;
use Doctrine\Model\ActionLog;
class LogManager
{
/**
* @var ObjectManager
*/
protected $om;
/**
* @var TokenStorage
*/
protected $tokenStorage;
/**
* @var User
*/
protected $user;
/**
* @var bool
*/
protected $disabled = false;
public function __construct(TokenStorage $tokenStorage = null)
{
$this->tokenStorage = $tokenStorage;
}
public function setEntityManager(ObjectManager $om)
{
$this->om = $om;
}
public function log(string $namespace, string $action, string $message = null, array $changeSet = null)
{
$log = new ActionLog;
$log
->setNamespace($namespace)
->setAction($action)
->setMessage($message)
->setChangeset($changeSet)
;
if ($this->isDisabled()) {
return;
}
if (!$log->getUser()) {
$user = $this->getUser();
$log->setUsername(
$user instanceof UserInterface
? $user->getUsername()
: ''
);
$user instanceof User && $log->setUser($user);
}
$this->om->persist($log);
$this->om->flush();
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
public function getUser(): ?UserInterface
{
if (!$this->user) {
if ($token = $this->tokenStorage->getToken()) {
$this->user = $token->getUser();
}
}
return is_string($this->user) ? null : $this->user;
}
public function disable(bool $disabled = true): self
{
$this->disabled = $disabled;
return $this;
}
public function isDisabled(): bool
{
return $this->disabled;
}
}
用户实体转储。如您所见,信息来自数据库。
User {#417 ▼
#name: "John Doe"
#email: "john_doe@example.com"
#password: "ec40577ad8057ee34ce0bb9414673bf3"
#createdAt: DateTime @1523344938 {#427 ▶}
#enabled: true
#lastLogin: null
#id: 1
}
# Associated database row
'1', 'John Doe', 'john_doe@example.com', 'ec40577ad8057ee34ce0bb9414673bf3', '2018-04-10 07:22:18', '1', '1', null
答案 0 :(得分:1)
断言是正确的,传递给ActionLog::setUser
方法的用户实例从ORM的角度来看并不是已知的参考。
会发生什么:该对象来自身份验证过程,该过程会根据每个请求(建议 yceruto )的会话存储中的用户数据进行反序列化em>用户实例已创建
我的自定义userProvider应该通过ORM刷新用户对象,但它没有,因此&#34;新参考&#34;在坚持。我不知道为什么虽然我的UserProvider实现允许它应该:
/**
* @var ObjectManager
*/
protected $em;
public function __construct(ObjectManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$user = $this->em->getRepository(User::class)->loadUserByUsername($username);
if ($user && $user->isAdmin()) {
return $user;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return UserInterface::class === $class;
}
这就是说,我在[{1}}方法的帮助下设法(暂时)使用ORM proxy mechanism来解决问题,这可以从重建后的对象完成session保存用户ID(主键)。
修复包括替换Log_manager服务中的以下指令:
Doctrine\ORM\EntityManager::getReference
对此有何想法?滥用? Github问题? 无论出于什么原因,评论都非常受欢迎。