停止Symfony 4通过EventListener更新关系实体

时间:2018-01-26 15:26:01

标签: php symfony doctrine-orm

我有一个具有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实体时调用订阅者,而不是与其有关系的实体?

1 个答案:

答案 0 :(得分:2)

preUpdate生命周期方法接收LifecycleEventArgs的子类型PreUpdateEventArgs。此类允许您访问正在更新的实体的更改集。

正如doctrine's documentation中所述,您可以使用它来根据实体中实际更改的内容更改preUpdate回调的行为。

在您的情况下,如果更新仅涉及关联字段,则需要使用$args->hasChangedField跳过加密部分。当且仅当加密过程中实际使用的字段发生更改时,您还可以转换此逻辑并执行加密部分。

另一方面,如果修改了拥有方,那么ManyToOne关联的反面不应该触发其preUpdate事件。讨论了这一点here。因此,如果触发了preUpdate实体的Api,则表示其更改集不为空,但它不能正常导致与scores字段相关的更改。