Reputation: 1921
I have a entity with a manyToOne relationship, simplified example here:
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;
}
The other side of the OneToMany looks like this:
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;
}
All this is good, however I wanted to put some simple encryption on the API Key so I am using openssl_encrypt and openssl_decrypt storing the iv in the Api table when encoding it to be able to decode. To do this automatically I have set up a a EventListener looking like this:
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']);
}
}
}
}
Again all this works absolutely fine, however my problem comes when I update the Score entity. It looks as because it has a ManyToOne relationship with Api the Subscriber is called and updates the ApiKey to a new encoded value with a new iv key.
This is usually fine but I need to use this in a CLI command where it is looping through the Api entities and calling a API service with them, but this update is causing the loop to use old outdated info and fails.
Do you know how to only call the Subscriber when directly modifying the Api entity, not one that it has a relationship with?
Upvotes: 0
Views: 603
Reputation: 1440
preUpdate
lifecycle method receives a sub type of LifecycleEventArgs
which is PreUpdateEventArgs
. This class allows you to access the change set of the entity being updated.
As explained in doctrine's documentation you can use that to change the behaviour of your preUpdate
callback based on what has actually changed in your entity.
In your case, you would need to use $args->hasChangedField
to skip the encryption part if the update only concerns associations fields. You could also turn this logic around and execute the encryption part if and only if fields that are actually used in the encryption process have changed.
On a side note, the inverse side of a ManyToOne association is not supposed to have its preUpdate
event triggered if the owning side is modified. This particular point is discussed here. Thus, if the preUpdate
of your Api
entity is triggered, that means that its change set is not empty, but it cannot be the cause of a change related to your scores
field normally.
Upvotes: 2