4
votes

My issue is, I'm having trouble grasping DiscriminatorColumn and DiscriminatorMap in Doctrine's Class Inheritance.

I have a products entity that is considered the parent class / table.

There are several child entities that inherit the product entity. (models, parts, and options)

I feel like I should be able to use the primary key to link both tables... But how do I do that with DiscriminatorColumn?

Here is the general idea of what I want to happen...

Fetch all model objects from database while inheriting product parent entity

SELECT object 
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id

Or... Fetch all part objects from database while inheriting product parent entity

SELECT object 
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id

Ideally I want this done using Doctrine instead of some custom SQL.

Do I need to setup a "type" column for the parent table so each row defines whether it's a part, model, or option?

Doctrine inheritance docs

2
I think you may be misunderstanding inheritance. Neither part not model nor option sounds like a product to me. Take a look at relations. I suspect a product consists of one or more parts and probably comes in different models with certain options.Cerad
Models, parts and options are all products that can be sold individually. They share over twenty attributes such as dimensions, cost, etc.payling
In that case, follow the documentation. You don't need to worry about the queries. The ORM will take care of that and deliver the appropiate entity type based on your discrimantor mapping. If you are new to doctrine then start with something simplier until you get comfortable with its design.Cerad

2 Answers

4
votes

Okay, I'll try to explain this as simple as possible.

Let's start with DiscriminatorColumn

Discriminator column is basically, as it says, a column in your database. Its used to store, a key, if you like which helps to identify what kind of object you're currently querying, based on your DiscriminatorMap configuration.

DiscriminatorMap is the way you map each of those keys to an entity. You said you have the following

  • Product [parent]
  • Model [child of parent]
  • Part [child of parent]
  • Option [child of parent]

Then, your discriminator map should look something like this, for example:

@DiscriminatorMap({
    "model" = "AppBundle\Entity\Model", 
    "Part" = "AppBundle\Entity\Part",
    "Option" = "AppBundle\Entity\Option"
})

Always pay attention to your last definition in your mapping. The last line must end without a comma!

As of InheritanceType I would suggest you to use @InheritanceType("JOINED") because this will let you have single table for each of your child classes.

Every child class must extend your Product entity class, which is obviously the parent. Each child class must not define $id property, because of the inheritance mapping.

Then querying for records by specific type comes with the following query:

"SELECT product FROM AppBundle\Entity\Product product WHERE product INSTANCE OF AppBundle\Entity\Part"

The query will search only for records mapped to this entity only.

If you have any questions, don't hesitate to ask.

Edit as of new comment

-----------------------

A little bit more explanation. You do not need to create any extra property/column in your entity mappings. The moment you add this annotation @DiscriminatorColumn(name="discr", type="string") doctrine will create that column automatically for you. The column from this example would be named discr with type of VARCHAR.

I still don't understand what is used to join the tables. How does doctrine know to link the ids between the product and model

About this part. If you use @InheritanceType("JOINED") this would mean that your GeneratedValue ID would be set in your main entity - Product. Then each of the child entities that extend Product would automatically get the same ID, which is why you don't need to specify $id property in your child entities.

Lastly, how can you check which entity type you're currently viewing for example. Consider the following scenario, each of your child entities extends Product and we will perform a dummy search for a record:

$product = $entityManager->find('AppBundle:Product', 1); // example

Now, if you actually go and do a var_dump($product) you will notice something interesting. The object would be an instance of either Model,Part or Option because each of these entities are defined in your discriminator map and Doctrine automatically maps your records based on that. Later, this can come handy in situations like this:

if( $product instanceof \AppBundle\Entity\Part ) {
    // do something only if that record belongs to part.
}
4
votes

If you want to use DiscriminatorMap for Doctrine, so you should use Doctrine, but not SQL.

Basic setup is:

/**
 * @ORM\Table(name="product")
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ProductRepository")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="productType", type="string")
 * @ORM\DiscriminatorMap({
 *   "Product" = "Product",
 *   "Model" = "Model",
 *   "Part" = "Part",
 *   "Option" = "Option",
 * })
 */
class Product
{
...
}

MyApp\ProductBundle\Entity\Model

/**
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ModelRepository")
 */
class Model extends Product 
{
}

MyApp\ProductBundle\Entity\Part

/**
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\PartRepository")
 */
class Part extends Product 
{
}

MyApp\ProductBundle\Entity\Option

/**
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\OptionRepository")
 */
class Option extends Product 
{
}

Then if you need to get all products at controller

 $em = $this->getDoctrine()->getManager(); 
 $repo = $em->getRepository("MyAppProductBundle:Product");
 $products = $repo->findAll();

Then if you need select all models, just setup proper repository

 $repo = $em->getRepository("MyAppProductBundle:Model");
 $models = $repo->findAll();