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

hel*_*ete 5 php doctrine-orm

我遇到了以下问题.应用程序需要能够克隆Season具有所有相关实体的实体.我在这个伟大的问题上受到启发- 一切都按照它应有的方式运作,但ManyToMany在路上存在关系问题.

请查看附图,其中显示了数据库图表的一小部分,显示了我遇到问题的部分.

数据库图表段 - 多对多关系

我想达到的状态是有一个克隆一个的Price绑定到实体的现有 Offer实体.说清楚 - 我不能也绝不能克隆Offer实体,实体的新克隆实例Price必须绑定到主Price实体实例绑定的同一个实例.

offer_price克隆之前表的示例内容

 offer_id | price_id                                                        
----------+----------                                                       
       47 |       77                                                        
Run Code Online (Sandbox Code Playgroud)

offer_price克隆后表的预期内容

 offer_id | price_id                                                        
----------+----------                                                       
       47 |       77                                                        
       47 |       79                                                        
Run Code Online (Sandbox Code Playgroud)

...假设PriceID 77是主记录Price,ID 79是绑定到同一Offer记录的新克隆实例.

实体定义 - 尽可能简化

价钱

/**                                                                         
 * @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;                                                       
    }                                                                       


    ...                                                                     
}                                                                           
Run Code Online (Sandbox Code Playgroud)

提供

/**                                                                         
 * @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();                              
    }                                                                       

    ...                                                                     
}                                                                  
Run Code Online (Sandbox Code Playgroud)

季节

/**                                                                         
 * @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;                         

            ...                                                             

        }                                                                   
    }                                                                       

    ...                                                                     
}                                                                           
Run Code Online (Sandbox Code Playgroud)

所处的状态是我拥有所需的关系中的所有对象,但只有在整个集合被持久化之前.通过持久保存父对象(Season)来刷新所有对象之后,除了ManyToMany绑定表之外,所有其他对象都会保持级联,除了没有添加新记录的绑定表.

到目前为止我在应用程序中使用的解决方案非常脏.刷新所有持久化对象后,我只是迭代Offer绑定到Price实例的记录(因为它们正确地相互绑定)并存储所有随后被手动插入数据库的ID.这个解决方案显然不理想,非常脆弱.

...                                                                         
/**                                                                         
 * 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 ?? [];                                              
}                                                                           
...                                                                         
Run Code Online (Sandbox Code Playgroud)

因此,我对如何坚持这样的关系感兴趣.我认为有一个优雅的解决方案我只是missig,因为这些操作在现实世界的场景中非常普遍.但也许Doctrine2无法做到这一点,"你必须自己做,因为Doctrine无法帮助你"也可能是一个有效的答案(这将使ORM相当无用的恕我直言).

我想补充-的情况下,对象上在两侧ManyToMany关系正在新创建和坚持,一切正常,因为它应该,所以我推测的绑定表ManyToMany关系正确注解.

PHP版本7.0.22
Doctrine2 ORM版本2.4.8

注意:我已经读过这个问题,但它没有解决同样的问题.

Fab*_*les 2

对于您的问题:这是因为您没有将报价对象链接到价格克隆(侧面需要mappedBy)。尝试这样的事情:

/**                                                                     
 * 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;                         

        ...                                                             

    }                                                                   
}
Run Code Online (Sandbox Code Playgroud)

对于你丑陋的部分:这个问题已经被问过并且答案已经提出了这个