我有一个具有多ToOne关系的实体,这里有简化的例子:
class Api
{
/**
* @ORMOneToMany(targetEntity="AppEntityScore", mappedBy="api")
*/
private $scores;
public function __construct()
{
$this->scores = new ArrayCollection();
}
/**
* @ORMColumn(type="string", length=400, nullable=true)
*/
private $apiKey;
/**
* @return mixed
*/
public function getApiKey() {
return $this->apiKey;
}
/**
* @param mixed $apiKey
*/
public function setApiKey( $apiKey ): void {
$this->apiKey = $apiKey;
}
OneToMany的另一面看起来像这样:
class Score
{
/**
* @ORMManyToOne(targetEntity="AppEntityApi", inversedBy="scores")
* @ORMJoinColumn(nullable=true)
*/
private $api;
/**
* @ORMColumn(type="decimal", precision=2, scale=5, nullable=true)
*/
private $highScore;
/**
* @return mixed
*/
public function getHighScore()
{
return $this->highScore;
}
/**
* @param mixed $highScore
*/
public function setHighScore($highScore): void
{
$this->highScore= $highScore;
}
所有这些都很好,但是我想在 API 密钥上进行一些简单的加密,所以我使用 openssl_encrypt openssl_decrypt并在编码时将 iv 存储在 Api 表中以便能够解码。为了自动执行此操作,我设置了一个事件侦听器,如下所示:
class ApiSubscriber implements EventSubscriber
{
private $encryption;
public function __construct(Encryption $encryption) {
$this->encryption = $encryption;
}
public function getSubscribedEvents()
{
return array(
'prePersist',
'preUpdate',
'postLoad'
);
}
public function prePersist(LifecycleEventArgs $args)
{
$this->index($args);
}
public function preUpdate(LifecycleEventArgs $args)
{
$this->index($args);
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if ($entity instanceof Api) {
$apiSecret = $entity->getApiSecret();
$iv = $entity->getIv();
$encodedSecret = $this->encryption->decrypt($apiSecret, $iv);
$entity->setApiSecret($encodedSecret);
}
}
public function index(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if ($entity instanceof Api) {
$apiSecret = $entity->getApiKey();
$encodedSecret = $this->encryption->encrypt($apiSecret);
$entity->setApiSecret($encodedSecret['encodedString']);
$entity->setIv($encodedSecret['iv']);
if($encodedSecret['success'])
{
$entity->setApiKey($encodedSecret['encodedString']);
$entity->setIv($encodedSecret['iv']);
}
}
}
}
同样,所有这些都可以正常工作,但是当我更新 Score 实体时,我的问题就出现了。它看起来因为它与 API 具有 ManyToOne 关系,所以调用订阅者并使用新的 iv 键将 ApiKey 更新为新的编码值。
这通常很好,但我需要在 CLI 命令中使用它,它循环遍历 Api 实体并用它们调用 API 服务,但此更新导致循环使用旧的过时信息并失败。
您是否知道如何仅在直接修改 API 实体时调用订阅服务器,而不是与其有关系的实体?
preUpdate
生命周期方法接收LifecycleEventArgs
子类型,即PreUpdateEventArgs
。此类允许您访问正在更新的实体的更改集。
如 doctrine 文档中所述,您可以使用它根据实体中实际更改的内容来更改preUpdate
回调的行为。
在您的情况下,如果更新仅涉及关联字段,则需要使用$args->hasChangedField
跳过加密部分。您还可以扭转此逻辑,当且仅当加密过程中实际使用的字段已更改时执行加密部分。
附带说明一下,如果修改了拥有方,则不应触发 ManyToOne 关联的反向侧preUpdate
事件。这里讨论这一点。因此,如果触发了Api
实体的preUpdate
,则意味着其更改集不为空,但它不能成为与scores
字段相关的更改的正常原因。