Search code examples
doctrinesymfony4discriminator

How does Doctrine discriminator know which entity to use from the map


I got a task of writing a discriminator today and it's my first time to learn about how Doctrine discriminator works. After reading the docs here's what I've done.

I need to create a command to change blog/post author and then also update the action entity about the event.

Action entity:

/**
 * @ORM\Table(name="action")
 * @ORM\Entity()
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discriminator", type="string")
 * @ORM\DiscriminatorMap({"action" = "Action", "blog" = "Blog", "post" = "Post"})
 */
class Action
{ ...

Somewhere in the eventCommand:

...
$action = new Action();
$action->setType('author_change');

$this->em->persist($action);

This all working after running the command. The only issue is that Action discriminator saves 'action' everytime, instead of either 'blog' or 'post'.

The column does not change even if I force it like this:

$action->setDiscriminator('blog');

So I think my question is how does Action knows and how should I be able to trigger which discriminator to use from the map?


Solution

  • Things to check:

    • your parent entity is Action, make sure Blog and Post both extend it
    • all properties in your Action class should be protected if you want your subclasses to be able to access them (this includes the id)

    I tried your setup with the following classes:

    Action:

    <?php
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity(repositoryClass="App\Repository\ActionRepository")
     * @ORM\InheritanceType("JOINED")
     * @ORM\DiscriminatorColumn(name="discriminator", type="string")
     * @ORM\DiscriminatorMap({"action" = "Action", "blog" = "Blog", "post" = "Post"})
     */
    class Action
    {
        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        protected $id;
    
        /**
         * @ORM\Column(type="string", length=255, nullable=true)
         */
        protected $name;
    
        // ... getters and setters for $id and $name (notice they are also protected, not private !!!)
    }
    

    Blog:

    <?php
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity(repositoryClass="App\Repository\BlogRepository")
     */
    class Blog extends Action
    {
        /**
         * @var string
         * @ORM\Column(name="blog_prop", length=255, nullable=true)
         */
        private $blogProp;
    
        // ... getters and setters for $blogProp
    }
    

    Post:

    <?php
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity(repositoryClass="App\Repository\PostRepository")
     */
    class Post extends Action
    {
        /**
         * @var string
         * @ORM\Column(name="post_prop", length=255, nullable=true)
         */
        private $postProp;
    
        // .... getters and setters for $postProp
    }
    

    Doctrine will create a separate table for each of your entities. When storing a child entity, a row will also be added in the parent entity, populated with the common (inherited) fields. The properties that are unique to the child entities will be inserted in their respective tables. This results in the following database structure:

    mysql> describe action;
    +---------------+--------------+------+-----+---------+----------------+
    | Field         | Type         | Null | Key | Default | Extra          |
    +---------------+--------------+------+-----+---------+----------------+
    | id            | int(11)      | NO   | PRI | NULL    | auto_increment |
    | name          | varchar(255) | YES  |     | NULL    |                |
    | discriminator | varchar(255) | NO   |     | NULL    |                |
    +---------------+--------------+------+-----+---------+----------------+
    
    
    mysql> describe blog;
    +-----------+--------------+------+-----+---------+-------+
    | Field     | Type         | Null | Key | Default | Extra |
    +-----------+--------------+------+-----+---------+-------+
    | id        | int(11)      | NO   | PRI | NULL    |       |
    | blog_prop | varchar(255) | YES  |     | NULL    |       |
    +-----------+--------------+------+-----+---------+-------+
    
    
    mysql> describe post;
    +-----------+--------------+------+-----+---------+-------+
    | Field     | Type         | Null | Key | Default | Extra |
    +-----------+--------------+------+-----+---------+-------+
    | id        | int(11)      | NO   | PRI | NULL    |       |
    | post_prop | varchar(255) | YES  |     | NULL    |       |
    +-----------+--------------+------+-----+---------+-------+
    

    And the controller:

    /**
     * @Route("/test", name="test")
     */
    public function test()
    {
        $manager = $this->getDoctrine()->getManager();
    
        $action = new Action();
        $action->setName('my action');
    
        $blog = new Blog();
        $blog
            ->setName('my blog')
            ->setBlogProp('my blog prop');
    
        $post = new Post();
        $post
            ->setName('my post')
            ->setPostProp('my post prop');
    
    
    
        $manager->persist($action);
        $manager->persist($blog);
        $manager->persist($post);
    
        $manager->flush();
    
    }
    

    After running the controller code:

    mysql> select * from action;
    +----+-----------+---------------+
    | id | name      | discriminator |
    +----+-----------+---------------+
    |  1 | my post   | post          |
    |  2 | my blog   | blog          |
    |  3 | my action | action        |
    +----+-----------+---------------+
    3 rows in set (0.00 sec)
    
    mysql> select * from blog;
    +----+--------------+
    | id | blog_prop    |
    +----+--------------+
    |  2 | my blog prop |
    +----+--------------+
    1 row in set (0.00 sec)
    
    mysql> select * from post;
    +----+--------------+
    | id | post_prop    |
    +----+--------------+
    |  1 | my post prop |
    +----+--------------+
    1 row in set (0.00 sec)