我有一个具有manyToOne关系的实体,简化示例如下:
class Api
{
/**
* @ORM\OneToMany(targetEntity="App\Entity\Score", mappedBy="api")
*/
private $scores;
public function __construct()
{
$this->scores = new ArrayCollection();
}
/**
* @ORM\Column(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
{
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Api", inversedBy="scores")
* @ORM\JoinColumn(nullable=true)
*/
private $api;
/**
* @ORM\Column(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 Key上加上一些简单的加密,所以我使用openssl_encrypt和openssl_decrypt在编码时将iv存储在Api表中以便能够解码。为了自动执行此操作,我设置了一个如下所示的EventListener:
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实体时调用订阅者,而不是与其有关系的实体?
答案 0 :(得分:2)
preUpdate
生命周期方法接收LifecycleEventArgs
的子类型PreUpdateEventArgs
。此类允许您访问正在更新的实体的更改集。
正如doctrine's documentation中所述,您可以使用它来根据实体中实际更改的内容更改preUpdate
回调的行为。
在您的情况下,如果更新仅涉及关联字段,则需要使用$args->hasChangedField
跳过加密部分。当且仅当加密过程中实际使用的字段发生更改时,您还可以转换此逻辑并执行加密部分。
另一方面,如果修改了拥有方,那么ManyToOne关联的反面不应该触发其preUpdate
事件。讨论了这一点here。因此,如果触发了preUpdate
实体的Api
,则表示其更改集不为空,但它不能正常导致与scores
字段相关的更改。