doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.95k stars 2.52k forks source link

One-To-One, Self-referencing not working #7273

Closed AntonioCS closed 6 years ago

AntonioCS commented 6 years ago

Bug Report

Q A
BC Break no

doctrine/annotations v1.3.1 Docblock Annotations Parser doctrine/cache v1.6.1 Caching library offering an object-oriented API for many cache backends doctrine/collections v1.4.0 Collections Abstraction library doctrine/common v2.7.2 Common Library for Doctrine projects doctrine/data-fixtures v1.2.2 Data Fixtures for all Doctrine Object Managers doctrine/dbal v2.5.12 Database Abstraction Layer doctrine/doctrine-bundle 1.6.7 Symfony DoctrineBundle doctrine/doctrine-cache-bundle 1.3.0 Symfony Bundle for Doctrine Cache doctrine/doctrine-fixtures-bundle v2.4.0 Symfony DoctrineFixturesBundle doctrine/doctrine-migrations-bundle v1.2.1 Symfony DoctrineMigrationsBundle doctrine/inflector v1.1.0 Common String Manipulations with regard to casing and singular/plural rules. doctrine/instantiator 1.0.5 A small, lightweight utility to instantiate objects in PHP without invoking their constructors doctrine/lexer v1.0.1 Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. doctrine/migrations v1.5.0 Database Schema migrations using Doctrine DBAL doctrine/orm v2.5.6 Object-Relational-Mapper for PHP oro/doctrine-extensions 1.2.0 Doctrine Extensions for MySQL and PostgreSQL.

Summary

Doctrine always sets parent to null in database even when parent is set to a valid entity.

I have the following entity orm:

ModelBundle\Entity\Category:
    type: entity
    table: category
    indexes:
        fki_parent_to_id:
            columns:
                - parent_id
    uniqueConstraints:
        uniq_64c19c1989d9b62:
            columns:
                - slug
        uniq_name:
            columns:
                - name
    id:
        id:
            type: integer
            nullable: false
            options:
                unsigned: false
            id: true
            generator:
                strategy: SEQUENCE
    fields:
        name:
            type: string
            nullable: false
            length: 255
            options:
                fixed: false
        slug:
            type: string
            nullable: false
            length: 255
            options:
                fixed: false
        description:
            type: string
            nullable: true
            length: 255
            options:
                fixed: false
        isActive:
            type: boolean
            nullable: false
            options:
                default: false
            column: is_active
        displayOrder:
            type: integer
            nullable: true
            options:
                unsigned: false
            column: display_order
    oneToOne:
        parent:
            targetEntity: Category
            joinColumns:
                parent_id:
                    referencedColumnName: id

    lifecycleCallbacks: {  }

Here is the entity class:

<?php

namespace ModelBundle\Entity;

use Doctrine\Common\Collections\Collection;
use JMS\Serializer\Annotation\Expose;

use Symfony\Cmf\Bundle\SeoBundle\Extractor\TitleReadInterface;
use Symfony\Cmf\Bundle\SeoBundle\Extractor\DescriptionReadInterface;
use Symfony\Cmf\Bundle\SeoBundle\Extractor\ExtrasReadInterface;

class Category implements ExtrasReadInterface, TitleReadInterface, DescriptionReadInterface
{
    /** @var int */
    private $id;

    /** @var string */
    private $slug;

    /**
     * @Expose
     *
     * @var string
     */
    private $name;

    /** @var bool */
    protected $isActive;

    /** @var string */
    private $description;

    /** @var Collection */
    private $products;

    /** @var int */
    private $displayOrder;

    /**
     * @var Category
     */
    private $parent;

    public function getId(): int
    {
        return $this->id;
    }

    public function setName(string $name): Category
    {
        $this->name = $name;
        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setIsActive(bool $isActive): Category
    {
        $this->isActive = $isActive;
        return $this;
    }

    public function getIsActive(): bool
    {
        return $this->isActive;
    }

    public function setDescription(string $description): Category
    {
        $this->description = $description;
        return $this;
    }

    public function getDescription(): string
    {
        return $this->description ?: '';
    }

    public function __construct()
    {
        $this->products = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function addProduct(Product $products): Category
    {
        $this->products[] = $products;
        return $this;
    }

    public function removeProduct(Product $products)
    {
        $this->products->removeElement($products);
    }

    public function getProducts(): Collection
    {
        return $this->products;
    }

    public function setSlug(string $slug): Category
    {
        $this->slug = $slug;
        return $this;
    }

    public function getSlug(): string
    {
        return $this->slug ?: '';
    }

    public function getSeoTitle(): string
    {
        return $this->getName();
    }

    public function getSeoDescription(): string
    {
        return $this->getDescription();
    }

    public function getSeoExtras(): array
    {
        return [
            'property' => [
                'og:title'       => $this->name,
                'og:description' => $this->description,
                'og:type'        => 'product:category'
            ],
        ];
    }

    function getDisplayOrder(): ?int
    {
        return $this->displayOrder;
    }

    public function setDisplayOrder($displayOrder): Category
    {
        $this->displayOrder = $displayOrder;
        return $this;
    }

    public function getParent() : Category
    {
        return $this->parent;
    }

    public function setParent(Category $parent): void
    {
        $this->parent = $parent;
    }

}

You can see that I have the parent (parent_id) set as a foreign_key that refers to id. This is similar to what is here in the docs: https://www.doctrine-project.org/projects/doctrine-orm/en/2.5/reference/association-mapping.html#one-to-one-self-referencing There is no yaml but I got this yaml from doctrine (using the generation commands).

The issue is that no matter what I do I can never save what is set in the parent. Example code:


        $em = $this->get('doctrine')->getManager();
        $repo = $em->getRepository(Category::class);

        $newCat = new Category();
        $newCat->setName('Test11');
        $newCat->setSlug('slug1');
        $newCat->setDescription('desc1');
        $newCat->setIsActive(true);
        $newCat->setDisplayOrder(1);
        $newCat->setParent($repo->find(11));

        var_dump($newCat);

        $em->persist($newCat);
        $em->flush();

The $repo->find(11) returns a Category entity that has a parent (I set it manually in the database) but the parent field is NULL. (I have also tried with an entity that has no parent, same result) Here is the var_dump:

object(ModelBundle\Entity\Category)[421]
  private 'id' => null
  private 'slug' => string 'slug1' (length=42)
  private 'name' => string 'Teste11' (length=33)
  protected 'isActive' => boolean true
  private 'description' => string 'desc1' (length=5)
  private 'products' => 
    object(Doctrine\Common\Collections\ArrayCollection)[422]
      private 'elements' => 
        array (size=0)
          empty
  private 'displayOrder' => int 1
  private 'parent' => 
    object(ModelBundle\Entity\Category)[435]
      private 'id' => int 11
      private 'slug' => string 'entertaining' (length=12)
      private 'name' => string 'Browse Entertaining' (length=19)
      protected 'isActive' => boolean false
      private 'description' => string 'Selection of great food for any occasion.' (length=41)
      private 'products' => 
        object(Doctrine\ORM\PersistentCollection)[458]
          private 'snapshot' => 
            array (size=0)
          protected 'initialized' => boolean false
      private 'displayOrder' => null
      private 'parent' => null

This code create a new category in the database but the parent field is set to NULL.

What am I doing wrong?

Current behavior

Parent is not set in the DB but is being set in the entity (explanations above)

How to reproduce

Example code provided above

Expected behavior

Parent being set and saved to DB

AntonioCS commented 6 years ago

Cache issues, please ignore