错误的“通过关系找到的新实体” - 对象实际存在于数据库

时间:2018-04-10 16:12:21

标签: symfony doctrine-orm orm symfony4

我们有一项服务,其目的是将用户操作记录到数据库中。底层实体 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

1 个答案:

答案 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问题? 无论出于什么原因,评论都非常受欢迎。