我正在编写一个严重依赖数据夹具的Symfony 2单元测试。作为一种捷径,我连接了一个方法,可以访问夹具加载器的ReferenceRepository
,这样我就可以在我的测试中访问共享实体。
然而,当我从ReferenceRepository
中拉出一个对象时,它没有任何关系,即使我将它们保留在数据夹具中。
奇怪的是,ReferenceRepository
中有一些代码似乎正在剥离这些关系,我不明白为什么会这样做(更不用说如何防止它)。
举个例子,这是数据夹具的样子:
public function load(ObjectManager $manager)
{
$project = new Project();
// ... populate fields ...
/* Add one detail field to the Project. */
$detail = new ProjectDetail();
// ... populate fields ...
$project->addDetail($detail);
$manager->persist($project);
$manager->flush();
$this->addReference('project-onedetail', $project);
}
在我的测试用例中,我正在做一些(或多或少)这样的事情:
$project =
$this->fixtureLoader->getReferenceRepository()
->getReference('project-onedetail');
当我在测试用例中调用该方法来获取此Project
对象时,我注意到一些奇怪的行为:
来自Doctrine\Common\DataFixtures\ReferenceRepository
(已添加评论):
public function getReference($name)
{
$reference = $this->references[$name];
// At this point, $reference contains the Project object with related ProjectDetail.
// It would be awesome if the method would just return $reference...
$meta = $this->manager->getClassMetadata(get_class($reference));
$uow = $this->manager->getUnitOfWork();
if (!$uow->isInIdentityMap($reference) && isset($this->identities[$name])) {
// ... but instead it goes into this conditional....
$reference = $this->manager->getReference(
$meta->name,
$this->identities[$name]
);
// ... and now $reference->getDetails() is empty! What just happened??
$this->references[$name] = $reference; // already in identity map
}
return $reference;
}
ReferenceRepository->getReference()
发生了什么?为什么相关对象会从$reference
中删除,如何防止这种情况?
答案 0 :(得分:1)
夹具加载器运行后,它会清除UnitOfWork的身份图。
请参阅\Doctrine\Common\DataFixtures\Executor\AbstractExecutor
:
public function load(ObjectManager $manager, FixtureInterface $fixture)
{
...
$fixture->load($manager);
$manager->clear();
}
因此,夹具加载程序完成后,!$uow->isInIdentityMap($reference)
中的条件ReferenceRepository->getReference()
将始终评估为false
。
您可以通过清除ReferenceRepository->$identities
来解决此问题。不幸的是,你没有直接访问这个数组,所以你需要做一些像kludgy这样的事情:
/* @kludge The fixture loader clears out its UnitOfWork object after
* loading each fixture, so we also need to clear the
* ReferenceRepository's identity map.
*/
$repository = $this->fixtureLoader->getReferenceRepository();
$identities = array_keys($repository->getIdentities());
foreach($identities as $key)
{
$repository->setReferenceIdentity($key, null);
}
但是,如果你这样做,如果你在测试装置中设置了相关的对象,你可能会遇到一些讨厌的ORMInvalidArgumentException
:
Doctrine \ ORM \ ORMInvalidArgumentException:通过关系“...”找到了一个新实体,该关系未配置为为实体:url级联持久化操作。要解决此问题:在此未知实体上显式调用EntityManager#persist()或在映射中配置级联持久保存此关联,例如@ManyToOne(..,cascade = {“persist”})。
最终,如果你想让它正常工作,你需要改变你在测试用例中使用的fixture执行器的行为,这样在加载fixture之后它就不会清除管理器:
/** Executes data fixtures for unit tests.
*/
class TestExecutor extends ORMExecutor
{
/** Load a fixture with the given persistence manager.
*
* @param ObjectManager|EntityManager $manager
* @param FixtureInterface $fixture
*/
public function load(ObjectManager $manager, FixtureInterface $fixture)
{
/** @kludge Unfortunately, we have to copy-paste a bit of code.
*
* The only difference between this method and AbstractExecutor->load()
* is that we don't call $manager->clear() when we're done loading.
*/
if($this->logger)
{
$prefix = '';
if($fixture instanceof OrderedFixtureInterface)
{
$prefix = sprintf('[%d] ', $fixture->getOrder());
}
$this->log('loading ' . $prefix . get_class($fixture));
}
// additionally pass the instance of reference repository to shared fixtures
if($fixture instanceof SharedFixtureInterface)
{
$fixture->setReferenceRepository($this->referenceRepository);
}
$fixture->load($manager);
/* Do NOT clear the unit of work; we will keep managed entities so that
* they are available to tests.
*/
}
}