Symfony Doctrine 不会滋润整个链条

Symfony Doctrine does not hydrate the whole chain

我在下面尽可能地简化了我的 3 个实体,它显示了 Currency <- 1:1 -> Balance <- 1:N -> BalanceLog

的简单关系

Entity/Currency.php

/**
 * @ORM\Entity(repositoryClass=CurrencyRepository::class)
 */
class Currency
{
    /**
     * @ORM\Id
     * @ORM\Column(type="string", length=3)
     */
    private ?string $code;


    /**
     * @ORM\OneToOne(targetEntity="Balance", mappedBy="currency")
     **/
    private ?Balance $balance;

    // ...
}

Entity/Balance.php

/**
 * @ORM\Entity(repositoryClass=BalanceRepository::class)
 */
class Balance
{
    /**
     * @ORM\Id
     * @ORM\OneToOne(targetEntity="Currency", inversedBy="balance")
     * @ORM\JoinColumn(name="currency", referencedColumnName="code", nullable=false)
     **/
    private ?Currency $currency;
    
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\BalanceLog", mappedBy="balance")
     */
    private Collection $balance_logs;

    // ...
}

Entity/BalanceLog.php

/**
 * @ORM\Entity(repositoryClass=BalanceLogRepository::class)
 */
class BalanceLog
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Balance", inversedBy="balance_logs")
     * @ORM\JoinColumn(name="balance_currency", referencedColumnName="currency")
     **/
    private ?Balance $balance;

    // ...

}

当我打电话时出现问题:

$balanceLog = $this->getDoctrine()
            ->getRepository('App:BalanceLog')->findAll();

这会将 BalanceLog::$balance 合成为 Balance 类型的正确实例,但不会将 BalanceLog::$balance->currency 合成为 Currency 实例。相反,它只想使用 string

导致错误:

Typed property App\Entity\Balance::$currency must be an instance of App\Entity\Currency or null, string used

肮脏的修复是使 Balance::$currency 没有 ?Currency 的固定类型。然后它将接受字符串和代码“有效”。但这是不正确的。 Balance::$currency 应该是 Currency 类型,不是有时是字符串,有时是货币。

我尝试在 BalanceLogRepository 中创建自己的方法,无论出于何种原因,它都运行良好:

public function findByBalance(Balance $balance) : iterable
{

    $query = $this->createQueryBuilder('bl');

    $query->andWhere('bl.balance = :balance')
        ->setParameter('balance', $balance);

    return $query->getQuery()->getResult();
}

所以我更加困惑为什么默认的findAllfindBy不做递归水化

经过进一步调查,我发现了一个非常奇怪的行为:

如果我在前面添加此代码:

$balance = $this->getDoctrine()->getRepository('App:Balance')->find('USD');

前面

$balanceLog = $this->getDoctrine()->getRepository('App:BalanceLog')->findAll();

在我的控制器中,然后 错误消失了。好像 BalanceApp:Balance ORM 模式没有正确加载,直到我尝试直接先验地获取 Balance 对象。

我进行了一些调试,看起来 BalanceLog 没有创建完整的 Balance 实体实例,而是创建了一个代理。解决方案是向 BalanceLog class

添加预先加载
class BalanceLog
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Balance", inversedBy="balance_logs", fetch="EAGER")
     * @ORM\JoinColumn(name="balance_currency", referencedColumnName="currency")
     **/
    private ?Balance $balance;

    // ...

}

然后 UnitOfWork.php 不使用代理而是加载整个实体。

如果有人想知道为什么事先查询 Balance 可以使代码运行,那是因为 Doctrine 复杂的缓存机制。它为主键 USD 保存了 Balance 实例,然后在填充 BalanceLog 时,它使用该实例而不是创建代理。

虽然我仍然认为 Proxy 不应该从 Entity 强制严格类型 属性,但这是 Doctrine 开发人员决定的事情。