[DDC-1799] Doctrine's Reverse Engineering 1-n (one to many) association misunderstood as 1-1 (one to one) Created: 27/Apr/12  Updated: 27/May/12  Resolved: 27/May/12

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.1.6
Fix Version/s: 2.1.7, 2.2.3
Security Level: All

Type: Bug Priority: Critical
Reporter: simone adami Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None
Environment:

MAC OS X 10.6.8, Symfony 2.0.12, PHP 5.3.6, mysql server 5.5.9



 Description   

I found an odd behaviour of Doctrine's reverse engineering process, just create two simple tables tied by a simple 1-n relationship, take a look at the snap of the folowing SQL code:

    SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
    SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
    SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';
    
    DROP SCHEMA IF EXISTS `ACME` ;
    CREATE SCHEMA IF NOT EXISTS `ACME` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
    USE `ACME` ;
    
    -- -----------------------------------------------------
    -- Table `ACME`.`task`
    -- -----------------------------------------------------
    DROP TABLE IF EXISTS `ACME`.`task` ;
    
    CREATE  TABLE IF NOT EXISTS `ACME`.`task` (
      `id_task` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
      `description` VARCHAR(45) NULL ,
      PRIMARY KEY (`id_task`) )
    ENGINE = InnoDB;
    
    
    -- -----------------------------------------------------
    -- Table `ACME`.`tag`
    -- -----------------------------------------------------
    DROP TABLE IF EXISTS `ACME`.`tag` ;
    
    CREATE  TABLE IF NOT EXISTS `ACME`.`tag` (
      `id_tag` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
      `name` VARCHAR(50) NULL ,
      `task_id` INT UNSIGNED NOT NULL ,
      PRIMARY KEY (`id_tag`) ,
      INDEX `fk_tag_task` (`task_id` ASC) ,
      CONSTRAINT `fk_tag_task`
        FOREIGN KEY (`task_id` )
        REFERENCES `ACME`.`task` (`id_task` )
        ON DELETE NO ACTION
        ON UPDATE NO ACTION)
    ENGINE = InnoDB;
    
    
    
    SET SQL_MODE=@OLD_SQL_MODE;
    SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
    SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

I have a Symfony2 netbeans project at

> /Applications/MAMP/htdocs/Acme

and from that location, according to

> http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html

in a terminal I did:

    $ ./../../bin/php/php5.3.6/bin/php app/console doctrine:mapping:convert yml ./src/Acme/TaskBundle/Resources/config/doctrine/ --from-database --force
    Processing entity "Tag"
    Processing entity "Task"
    
    Exporting "yml" mapping information to "/Applications/MAMP/htdocs/Acme/src/Acme/TaskBundle/Resources/config/doctrine"
    
    $ ./../../bin/php/php5.3.6/bin/php app/console doctrine:mapping:import Acme\TaskBundle yml
    Importing mapping information from "default" entity manager
      > writing /Applications/MAMP/htdocs/Acme/src/Acme/TaskBundle/Resources/config/doctrine/Tag.orm.yml
      > writing /Applications/MAMP/htdocs/Acme/src/Acme/TaskBundle/Resources/config/doctrine/Task.orm.yml
    
    $ ./../../bin/php/php5.3.6/bin/php app/console doctrine:generate:entities Acme\TaskBundle
    Generating entities for bundle "AcmeTaskBundle"
      > backing up Tag.php to Tag.php~
      > generating Acme\TaskBundle\Entity\Tag
      > backing up Task.php to Task.php~
      > generating Acme\TaskBundle\Entity\Task

The fact is that it only seems ok, because if you take a look at "Tag.orm.yml":

Tag.orm.yml
    Acme\TaskBundle\Entity\Tag:
      type: entity
      table: tag
      fields:
        idTag:
          id: true
          type: integer
          unsigned: false
          nullable: false
          column: id_tag
          generator:
            strategy: IDENTITY
        name:
          type: string
          length: 50
          fixed: false
          nullable: true
      oneToOne:
        task:
          targetEntity: Task
          cascade: {  }
          mappedBy: null
          inversedBy: null
          joinColumns:
            task_id:
              referencedColumnName: id_task
          orphanRemoval: false
      lifecycleCallbacks: {  }

It created a *oneToOne* relationship and not a *oneToMany* !

If you need any more confirmation, here are **Task.php** and **Tag.php**:

**Task.php**

Task.php
    <?php
    
    namespace Acme\TaskBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * Acme\TaskBundle\Entity\Task
     */
    class Task
    {
        /**
         * @var integer $idTask
         */
        private $idTask;
    
        /**
         * @var string $description
         */
        private $description;
    
    
        /**
         * Get idTask
         *
         * @return integer 
         */
        public function getIdTask()
        {
            return $this->idTask;
        }
    
        /**
         * Set description
         *
         * @param string $description
         */
        public function setDescription($description)
        {
            $this->description = $description;
        }
    
        /**
         * Get description
         *
         * @return string 
         */
        public function getDescription()
        {
            return $this->description;
        }
    }
Tag.php
***Tag.php***

    <?php
    
    namespace Acme\TaskBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * Acme\TaskBundle\Entity\Tag
     */
    class Tag
    {
        /**
         * @var integer $idTag
         */
        private $idTag;
    
        /**
         * @var string $name
         */
        private $name;
    
        /**
         * @var Acme\TaskBundle\Entity\Task
         */
        private $task;
    
    
        /**
         * Get idTag
         *
         * @return integer 
         */
        public function getIdTag()
        {
            return $this->idTag;
        }
    
        /**
         * Set name
         *
         * @param string $name
         */
        public function setName($name)
        {
            $this->name = $name;
        }
    
        /**
         * Get name
         *
         * @return string 
         */
        public function getName()
        {
            return $this->name;
        }
    
        /**
         * Set task
         *
         * @param Acme\TaskBundle\Entity\Task $task
         */
        public function setTask(\Acme\TaskBundle\Entity\Task $task)
        {
            $this->task = $task;
        }
    
        /**
         * Get task
         *
         * @return Acme\TaskBundle\Entity\Task 
         */
        public function getTask()
        {
            return $this->task;
        }
    }

linuxatico



 Comments   
Comment by simone adami [ 30/Apr/12 ]

This problem is encountered only in this case, 1-1 and n-m relationship are handled in the right way, has anyone else faced this problem too?

linuxatico

Comment by simone adami [ 07/May/12 ]

Hi all,
I wanna give the Doctrine community my full support to help fixing this bug, but I need someone who can give me an answer.....
I couldn't figure out in the source code which is the method executed when given the command

$ ./../../bin/php/php5.3.6/bin/php app/console doctrine:mapping:convert yml ./src/Acme/TaskBundle/Resources/config/doctrine/ --from-database --force

I will keep on looking for it, but some help will be appreciated,

linuxatico

Comment by simone adami [ 07/May/12 ]

Found in vendor/doctrine/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php
I'll see what I can do.

linuxatico

Comment by simone adami [ 09/May/12 ]

Even if I keep being ignored, I want to report a very useful discovery about this annoying bug: it's 100% related to the YAML conversion, because if you execute the first command

 $ ./../../bin/php/php5.3.6/bin/php app/console doctrine:mapping:convert xml ./src/Acme/TaskBundle/Resources/config/doctrine/ --from-database --force

Using XML instead of YML it works in the expected way. I wonder if the author of this code have written a unit test before integrating this function in the official release of Doctrine.... (ironic question)

linuxatico

Comment by Benjamin Eberlei [ 27/May/12 ]

This case was indeed not unit-tested, many-to-one and one-to-one were handled the same in YAML Exporter. No need to get picky about it though, we are investing our free time here.

Generated at Sun Nov 23 16:16:09 UTC 2014 using JIRA 6.2.3#6260-sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.