ReferenceRepository在调用getReference()时剥离相关实体

时间:2013-09-10 20:08:10

标签: symfony doctrine-orm

我正在编写一个严重依赖数据夹具的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中删除,如何防止这种情况?

1 个答案:

答案 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.
         */
    }
}