Home » Php » php – Polymorphic relationships with Doctrine2

php – Polymorphic relationships with Doctrine2

Posted by: admin July 12, 2020 Leave a comment

Questions:

How do I create traditional polymorphic relationships with Doctrine 2?

I have read a lot of answers that suggest using Single Table Inheritance but I can’t see how this would help in my situation. Here’s what I’m trying to do:

I have some utility entities, like an Address, an Email and a PhoneNumber.

I have some ‘contactable’ entities, like a Customer, Employer, Business. Each of these should contain a OneToMany relationship with the above utility entities.

Ideally, I’d like to create an abstract base class called ‘ContactableEntity’ that contains these relationships, but I know it is not possible to put OneToMany relationships in mapped superclasses with doctrine– that’s fine.

However, I am still at a loss at how I can relate these without massive redundancy in code. Do I make Address an STI type, with a ‘CustomerAddress’ subclass that contains the relationship directly to a Customer? Is there no way to reduce the amount of repetition?

How to&Answers:

Why not just make your base ContactableEntity concrete?

EDIT:

Just did a few experiments in a project I’ve done that uses CTI. I don’t see any reason that the same strategy wouldn’t work with STI.

Basically, I have something like:

/**                                                                                                                                                                                                                                                                                      
 * Base class for orders.  Actual orders are some subclass of order.                                                                                                                                                                                                                     
 *                                                                                                                                                                                                                                                                                       
 * @Entity                                                                                                                                                                                                             
 * @Table(name="OOrder")                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
 * @InheritanceType("JOINED")                                                                                                                                                                                                                                                            
 * @DiscriminatorColumn(name="discr", type="string")                                                                                                                                                                                                                                     
 * @DiscriminatorMap({"CAOrder" = "CAOrder", "AmazonOrder" = "AmazonOrder"})                                                                                                                                                                                                                                                                                                                                                                                      
 */
abstract class Order {
    /**           
     * CSRs can add notes to orders of any type                                                                                                                                                                                                                                                          
     * @OneToMany(targetEntity = "OrderNote", mappedBy = "order", cascade={"all"})                                                                                                                                                                                                         
     * @OrderBy({"created" = "ASC"})                                                                                                                                                                                                                                                         
     */
    protected $notes;

    // ...
}

/**                                                                                                                                                                                                                                                                                      
 * @Entity                                                                                                                                                                                                                                                                               
 */
class AmazonOrder extends Order {

  /**                                                                                                                                                                                                                                                                                    
   * @Column(type="string", length="20")                                                                                                                                                                                                                                                 
   */
  protected $amazonOrderId;

  // ...

}

/**                                                                                                                                                                                                                                                                                      
 * @Entity                                                                                                                                                                                                                                                                               
 */
class OrderNote {
    // ...

    /**                                                                                                                                                                                                                                                                                    
     * @ManyToOne(targetEntity="Order", inversedBy="notes")                                                                                                                                                                                                                                
     */
    protected $order;

    // ...
}

And it seems to work exactly as expected. I can get an OrderNote, and it’s $order property will contain some subclass of Order.

Is there some restriction on using STI that makes this not possible for you? If so, I’d suggest moving to CTI. But I can’t imagine why this wouldn’t work with STI.

Answer:

If the contactable entity shall be abstract (@MappedSuperclass) you’ll need to use the ResolveTargetEntityListener provided by Doctrine 2.2+.

It basically allows you to define a relationship by specifying an interface instead of a concrete entity. (Maybe you want to define/inherit several interfaces as you speak of multiple “contactables”). For instance you then can implement the interface in your abstract class or concrete class. Finally you’ll need to define/associate the concrete class (entity) to the related interface within the config.yml

An example can be found in the Symfony docs: http://symfony.com/doc/current/cookbook/doctrine/resolve_target_entity.html