Doctrine 2 Concrete Table Inheritance with Associations

2k views Asked by At

I have read Doctrine 2 Inheritance Mapping with Association but say if you needed Animal to be an entity in its own right, say for creating option lists.

In this case the Discriminator Column for a Pet is in the species column in the animal table.

Pet Database Schema

So the classes would be something like this.

class Animal
{
    $id;
    $species;
}

/**
 * @Table(name="animal")
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="species", type="string")
 * @DiscriminatorMap({"dog" = "Dog", "cat" = "Cat"})
 */
abstract class Pet
{
    $id
    $species;
}

class Dog extends Pet { }

class Cat extends Pet { }

class Owner 
{
    /**
     * @OneToMany(targetEntity="Pet")
     */
    $pets
}

I see a couple of problems.

  1. the animal table doesn't have a foreign key to owner. So the the $pets association isn't going to work.

  2. Animal and Pet are more or less that same thing. If some one changes something in Animal those changes wouldn't be reflected in any Pet subclasses.

A possible solution to the first problem would be to have an extra table called pet, but there is still an issue with the association, because the discriminator column is still in the animal table and duplicating the column in pet would break normalisation.

enter image description here

1

There are 1 answers

0
BenMorel On

I'm not sure to fully understand your problem, but I'll give it a try:

  • A Pet being an Animal, it makes sense to make it explicit in your class hierarchy. Note that the abstract class Pet can subclass the concrete class Animal. This way, you can still instantiate an Animal, a Dog or a Cat, but you can't instantiate a Pet;
  • The Animal root class can have its own discriminator value, which makes possible to persist it with Doctrine. You can then create and persist a Dog or a Cat, and even a generic Animal;
  • Animal needs a reference to its owner;
  • Owner now has a one-to-many relationship to Animal;
  • The species column is a discriminator, and adds no value to your domain model (the class name will tell you which Animal you're dealing with), so I would advise to remove this property.

Final note, do you really need a Pet class? If a Pet has nothing specific over an Animal (i.e. the class is empty), then you should probably remove it from your class hierarchy.

class Owner {
    /**
     * @OneToMany(targetEntity="Animal")
     */
    protected $animals;
}

/**
 * @Table(name="animal")
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="species", type="string")
 * @DiscriminatorMap({"animal" = "Animal", "dog" = "Dog", "cat" = "Cat"})
 */
class Animal {
    /** @Id @Column(type="integer") @GeneratedValue */
    protected $id;

    /** @ManyToOne(targetEntity="Owner") */
    protected $owner;
}

abstract class Pet extends Animal {
    // Do you really need this class?
}

class Dog extends Pet {
    // ...
}

class Cat extends Pet {
    // ...
}

About your database structure, I propose the following changes:

  • Move owner_id from Dog & Cat to the Animal table;
  • Remove animal_id from the Dog & Cat tables; they'll share the id value with the Animal table (one-to-one).