dongqing4774 2017-10-23 11:25
浏览 54
已采纳

深度克隆后未创建Doctrine2 ManyToMany记录

I have encountered the following problem. The application needs to be able to clone a Season entity with all of its related entities. I have inspired in this great question - and everything works as it should, but there is a problem with ManyToMany relation on the way.

Please take a look at the attached image depicting a small part of the database diagram showing the section I am having problems with.

database diagram segment - many to many relation

The state I want to achieve is to have a clone of a Price entity bound to an existing Offer entity. To put it clear - I cannot and must not clone the Offer entity, the new cloned instance of the Price entity has to be bound to the same instance the master Price entity instance is bound to.

Example contents of the offer_price table before cloning

 offer_id | price_id                                                        
----------+----------                                                       
       47 |       77                                                        

Intended contents of the offer_price table after cloning

 offer_id | price_id                                                        
----------+----------                                                       
       47 |       77                                                        
       47 |       79                                                        

... assuming the Price of ID 77 is the master record and the Price of ID 79 is the newly cloned instance bound to the same Offer record.

Entity definitions - simplified as possible

Price

/**                                                                         
 * @Entity                                                                  
 */                                                                         
class Price                                                                 
{                                                                           
    ...                                                                     

    /**                                                                     
     * @var \Doctrine\Common\Collections\Collection of Offer                
     * @ManyToMany(targetEntity="Offer", mappedBy="prices", cascade={"persist"})
     *                                                                      
     * @get                                                                 
     * @set                                                                 
     * @add                                                                 
     * @remove                                                              
     * @contains                                                            
     */                                                                     
    private $offers;                                                        

    /**                                                                     
     * Class construct                                                      
     *                                                                      
     * @return void                                                         
     */                                                                     
    public function __construct()                                           
    {                                                                       
        parent::__construct();                                              
        $this->offers = new ArrayCollection();                              
    }                                                                       


    /**                                                                     
     * Clone entity                                                         
     *                                                                      
     * @return void                                                         
     */                                                                     
    public function __clone()                                               
    {                                                                       
        if ($this->getId()) {                                               
            $this->setId(null);                                             
            $this->offers = new ArrayCollection();                          
        }                                                                   
    }                                                                       


    /**                                                                     
     * Add and offer into offers collection                                 
     *                                                                      
     * @param  Offer    $offer                                              
     * @return self                                                         
     */                                                                     
    public function addOffer(Offer $offer)                                  
    {
        $this->offers->add($offer);                                         

        return $this;                                                       
    }                                                                       


    ...                                                                     
}                                                                           

Offer

/**                                                                         
 * @Entity                                                                  
 */                                                                         
class Offer                                                                 
{                                                                           

    ...                                                                     

    /**                                                                     
     * @var \Doctrine\Common\Collections\Collection of Price                
     * @ManyToMany(targetEntity="Price", inversedBy="offers", cascade={"persist"})
     *                                                                      
     * @get                                                                 
     * @set                                                                 
     * @add                                                                 
     * @remove                                                              
     * @contains                                                            
     */                                                                     
    private $prices;                                                        

    /**                                                                     
     * Class construct                                                      
     *                                                                      
     * @return void                                                         
     */                                                                     
    public function __construct()                                           
    {                                                                       
        parent::__construct();                                              
        $this->prices = new ArrayCollection();                              
    }                                                                       

    ...                                                                     
}                                                                  

Season

/**                                                                         
 * @Entity                                                                  
 */                                                                         
class Season                                                                
{                                                                           

    ...                                                                     

    /**                                                                     
     * @var \Doctrine\Common\Collections\Collection of Price                
     * @OneToMany(targetEntity="Price", mappedBy="season", cascade={"persist", "remove"})
     *                                                                      
     * @get                                                                 
     * @set                                                                 
     * @add                                                                 
     * @remove                                                              
     * @contains                                                            
     */                                                                     
    private $prices;                                                        

    /**                                                                     
     * Class construct                                                      
     *                                                                      
     * @return void                                                         
     */                                                                     
    public function __construct()                                           
    {                                                                       
        parent::__construct();                                              
        $this->prices = new ArrayCollection();                              
    }                                                                       


    /**                                                                     
     * Clone entity                                                         
     *                                                                      
     * @return void                                                         
     */                                                                     
    public function __clone()                                               
    {                                                                       
        if ($this->getId()) {                                               
            $this->setId(null);                                             

            ...                                                             

            $priceClonedCollection = new ArrayCollection();                 
            foreach ($this->prices as $price) {                             
                $priceClone = clone $price;                                 
                $priceClone->setSeason($this);            
                foreach ($price->getOffers() as $offer) {                   
                    $priceClone->addOffer($offer);                          
                }                                                           
                $priceClonedCollection->add($priceClone);                   
            }                                                               
            $this->prices = $priceClonedCollection;                         

            ...                                                             

        }                                                                   
    }                                                                       

    ...                                                                     
}                                                                           

The state I am in is that I have all objects in a relation I need, but only until the whole set is persisted. After flushing all the objects by persisting the parent one (Season), all the others get cascade persisted as they should except the ManyToMany binding table, where no new records are being added.

The solution I employed in the application so far is quite dirty. After flushing all persisted objects I just iterate over the Offer records bound to the Price instance (since they are correctly bound to each other) and store all ids which are then being manually inserted into the database. This solution is obviously not ideal and quite fragile.

...                                                                         
/**                                                                         
 * Return an array consisting of mappings that have to be inserted manually 
 *                                                                          
 * @param  Season   $season                                                 
 * @return array                                                            
 */                                                                         
public function getCloneBindingHack(Season $clone)                          
{                                                                           
    foreach ($clone->getPrices() as $price) {                               
        foreach ($price->getOffers() as $offer) {                           
            $bindingHack[] = [                                              
                'offer_id' => $offer->getId(),                              
                'price_id' => $price->getId(),                              
            ];                                                              
        }                                                                   
    }                                                                       

    return $bindingHack ?? [];                                              
}                                                                           
...                                                                         

Therefore I am interested in how to persist relations like this. I presume there is an elegant solution I am just missig as these operations are quite common in real world scenarios. But perhaps Doctrine2 is not able to do this so "You have to do that yourself as Doctrine cannot help you" can also be a valid answer (which would render the ORM quite useless IMHO).

Just to add - in case objects on both sides of the ManyToMany relation are being newly created and persisted, everything works as it should so I presume the binding table of the ManyToMany relation is annotated correctly.

PHP version 7.0.22
Doctrine2 ORM version 2.4.8

Note: I have read this this question, but it does not address the same issue.

  • 写回答

1条回答 默认 最新

  • doubo4336 2017-10-23 13:28
    关注

    For your problem : it is because you don't have link your offer object to your price clone (it is required in the mappedBy side). Try something like this :

    /**                                                                     
     * Clone entity                                                         
     *                                                                      
     * @return void                                                         
     */                                                                     
    public function __clone()                                               
    {                                                                       
        if ($this->getId()) {                                               
            $this->setId(null);                                             
    
            ...                                                             
    
            $priceClonedCollection = new ArrayCollection();                 
            foreach ($this->prices as $price) {                             
                $priceClone = clone $price;                                 
                $priceClone->setSeason($this);            
                foreach ($price->getOffers() as $offer) {
                    $offer->addPrice($priceClone);                   
                    $priceClone->addOffer($offer);                          
                }                                                           
                $priceClonedCollection->add($priceClone);                   
            }                                                               
            $this->prices = $priceClonedCollection;                         
    
            ...                                                             
    
        }                                                                   
    }
    

    For your ugly part : this question has already been asked and an answer has proposed this package

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 phython如何实现以下功能?查找同一用户名的消费金额合并—
  • ¥15 ARIMA模型时间序列预测用pathon解决
  • ¥15 孟德尔随机化怎样画共定位分析图
  • ¥18 模拟电路问题解答有偿速度
  • ¥15 CST仿真别人的模型结果仿真结果S参数完全不对
  • ¥15 误删注册表文件致win10无法开启
  • ¥15 请问在阿里云服务器中怎么利用数据库制作网站
  • ¥60 ESP32怎么烧录自启动程序,怎么查看客户esp32板子上程序及烧录地址
  • ¥50 html2canvas超出滚动条不显示
  • ¥15 java业务性能问题求解(sql,业务设计相关)