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();
}
所以我更加困惑为什么默认的findAll
或findBy
不做递归水化
经过进一步调查,我发现了一个非常奇怪的行为:
如果我在前面添加此代码:
$balance = $this->getDoctrine()->getRepository('App:Balance')->find('USD');
前面
$balanceLog = $this->getDoctrine()->getRepository('App:BalanceLog')->findAll();
在我的控制器中,然后 错误消失了。好像 Balance
的 App: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 开发人员决定的事情。
我在下面尽可能地简化了我的 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();
}
所以我更加困惑为什么默认的findAll
或findBy
不做递归水化
经过进一步调查,我发现了一个非常奇怪的行为:
如果我在前面添加此代码:
$balance = $this->getDoctrine()->getRepository('App:Balance')->find('USD');
前面
$balanceLog = $this->getDoctrine()->getRepository('App:BalanceLog')->findAll();
在我的控制器中,然后 错误消失了。好像 Balance
的 App: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 开发人员决定的事情。