教义的find()和querybuilder()在PHPUnit测试中返回不同的结果

时间:2019-07-09 20:02:55

标签: symfony doctrine phpunit

在我的PHPUnit测试方法中使用Doctrine和Symfony:

// Change username for user #1 (Sheriff Woody to Chuck Norris)
$form = $crawler->selectButton('Update')->form([
    'user[username]' => 'Chuck Norris',
]);
$client->submit($form);

// Find user #1
$user = $em->getRepository(User::class)->find(1);
dump($user); // Username = "Sheriff Woody"

$user = $em->createQueryBuilder()
        ->from(User::class, 'user')
        ->andWhere('user.id = :userId')
        ->setParameter('userId', 1)
        ->select('
            user
        ')
        ->getQuery()
        ->getOneOrNullResult()
    ;
dump($user); // Username = "Chuck Norris"

为什么我的两种获取用户#1的方法返回不同的结果?

1 个答案:

答案 0 :(得分:1)

诊断/解释

假定* ,您之前已经在该函数中创建了要通过搜寻器编辑的User对象,并检查它是否在那里。这导致它成为一个受管实体。

数据的本质是不要与数据库神奇地自我同步,但是必须具备一定的自动性或执行某种同步方法。

find()方法将始终尝试使用缓存(除非显式关闭,另请参见边注)。 如果您明确调用getResult()(或其变种之一),则查询生成器将不会,因为您明确希望执行查询。执行其他查询可能会导致缓存没有被击中,产生当前结果。 (尽管它应该更新第一个用户对象...)[已更新,由于来自Arno Hilke的评论]

((((旁注:使对象保持同步是很困难的。。这主要是为了保持数据库的一致性,但是所有ACID都是必需的。数据库应假定它仅在第一次查询时就与状态一起使用,并且是数据库的唯一用户。除非必须满足其他约束并且可能发生不一致的读取,否则应提高隔离级别(另请参见:事务或更确切地说:隔离)。因此,通常不需要自动同步。Doctrine使用某些假设来提高性能(主要是:隔离/锁定是乐观的)。但是,在您的特定情况下,所有这些都是没有实际的问题...因为您实际上想要一个non-repeatable read。))))

(*,否则,您看到的行为将确实出乎意料)

解决方案

一种简单的解决方案是,通过调用$em->refresh($user)来主动和明确地同步来自数据库的数据,或者-在再次获取用户之前-调用$em->clear() ,它将分离所有实体(清除缓存,这可能会对性能产生明显影响),并允许您再次调用find并返回正确的结果。

请注意,分离实体意味着,先前从实体管理器返回的任何对象都应丢弃并再次获取(而不是通过刷新)。

替代解决方案1-一切都是请求

代替检查数据库,您可以对显示用户名并检查其名称已更改的页面执行不同的请求。

备用解决方案2-仅使用一个实体管理器

仅使用一个实体管理器(即:根据请求与服务器共享单元测试中的实体管理器/数据库)可能是一个合理的解决方案,但它有一系列问题。主要是省略的提交和刷新可能会避免检测。

备用解决方案3-使用多个实体管理器

由于服务器正在使用新的实体管理器来执行其工作,因此使用一个实体管理器来设置测试,从理论上讲-为了实际上正确地执行此操作,您应该创建另一个实体管理器来检查服务器的行为。

评论:替代解决方案1,2和3可以在最高隔离级别下使用,而最初的解决方案可能无效。