symfonyterminaldoctrine

Create a relation between two instances of the same entity(foreign key pointing toward the table it is in) in doctrine, symfony


and thanks you in advance for all the replies you will make It's my first message here, so don't hesitate to tell me if there is some information i forgot to give you in order to size up the problem.

I have a symfony project (symfony 6.1.12 on wamp 3.3.5, php 8.2.18) and i'm starting to (re)create my database with doctrine, i use the terminal and the doctrine's commands for it (php bin/console make:entity)

It is all working good when creating associations/relations between two distinct entity, but not when trying to make a relation to an entity from other instance(s) of the same one: when doing the migration, the foreign key don't generate in the physical table in the database.

I started looking at the migration file and find that the query to add the foreign key was not generating, so the problem is with the code the command make:entity generate into rhe src/Entity files. I sure think it is shall be some dumb mistake of mine, made some tests (create an OneToMany relation with the ManyToOne, writing code by hand, etc...) and tryed looking for some information online with no success (which is strange... is foreign key pointing toward their own table's entries not a thing to do in doctrine?), but i don't know what would be the problem here.

For exemple, here the first try i made to create an Entity that can have one father Entity and have multiple child entity.

Here is what i typed in the terminal:

C:\wamp64\www\symfony\le_capharnaum_mora>php bin/console make:entity

 Class name of the entity to create or update (e.g. TinyElephant):
 > entityTest

 created: src/Entity/EntityTest.php
 created: src/Repository/EntityTestRepository.php

In DoctrineHelper.php line 177:

  Attempted to load class "DisconnectedClassMetadataFactory" from namespace "Doctrine\ORM\Tools".
  Did you forget a "use" statement for another namespace?


make:entity [-a|--api-resource] [-b|--broadcast] [--regenerate] [--overwrite] [--] [<name>]


C:\wamp64\www\symfony\le_capharnaum_mora>php bin/console make:entity

 Class name of the entity to create or update (e.g. GentleChef):
 > entityTest

 Your entity already exists! So let's add some new fields!

 New property name (press <return> to stop adding fields):
 > name

 Field type (enter ? to see all types) [string]:
 >

 Field length [255]:
 >

 Can this field be null in the database (nullable) (yes/no) [no]:
 >

 updated: src/Entity/EntityTest.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > entityTestFather

 Field type (enter ? to see all types) [string]:
 > ManyToOne

 What class should this entity be related to?:
 > entityTest

 Is the EntityTest.entityTestFather property allowed to be null (nullable)? (yes/no) [yes]:
 >

 Do you want to add a new property to entityTest so that you can access/update EntityTest objects from it - e.g. $entityTest->getEntityTests()? (yes/no) [yes]:
 >

 A new property will also be added to the entityTest class so that you can access the related EntityTest objects from it.

 New field name inside entityTest [entityTests]:
 >

 updated: src/Entity/EntityTest.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 >



  Success!


 Next: When you're ready, create a migration with php bin/console make:migration


C:\wamp64\www\symfony\le_capharnaum_mora>php bin/console make:migration


 created: migrations/Version20240813111624.php


  Success!


 Review the new migration then run it with php bin/console doctrine:migrations:migrate
 See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html

C:\wamp64\www\symfony\le_capharnaum_mora>php bin/console doctrine:migrations:migrate

 WARNING! You are about to execute a migration in database "lecafarnaummora" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]:
 >

[notice] Migrating up to DoctrineMigrations\Version20240813111624
[notice] finished in 130.6ms, used 20M memory, 1 migrations executed, 3 sql queries

 [OK] Successfully migrated to version: DoctrineMigrations\Version20240813111624

And here is the src/EntityTest.php

<?php

namespace App\Entity;

use App\Repository\EntityTestRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: EntityTestRepository::class)]
class EntityTest
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $name = null;

    #[ORM\OneToMany(targetEntity: EntityTest::class, mappedBy: 'entityTestFather')]
    private Collection $entityTests;

    public function __construct()
    {
        $this->entityTests = new ArrayCollection();
    }

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

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

    public function setName(string $name): static
    {
        $this->name = $name;

        return $this;
    }

    /**
     * @return Collection<int, EntityTest>
     */
    public function getEntityTests(): Collection
    {
        return $this->entityTests;
    }

    public function addEntityTest(EntityTest $entityTest): static
    {
        if (!$this->entityTests->contains($entityTest)) {
            $this->entityTests->add($entityTest);
            $entityTest->setEntityTestFather($this);
        }

        return $this;
    }

    public function removeEntityTest(EntityTest $entityTest): static
    {
        if ($this->entityTests->removeElement($entityTest)) {
            // set the owning side to null (unless already changed)
            if ($entityTest->getEntityTestFather() === $this) {
                $entityTest->setEntityTestFather(null);
            }
        }

        return $this;
    }
}

Finally, here is the code of the migration file, with the foreign key for the relation missing?

<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20240813111624 extends AbstractMigration
{
    public function getDescription(): string
    {
        return '';
    }

    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE TABLE base_de_donnees (id INT AUTO_INCREMENT NOT NULL, nom VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB');
        $this->addSql('CREATE TABLE entity_test (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB');
        $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL, INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB');
    }

    public function down(Schema $schema): void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->addSql('DROP TABLE base_de_donnees');
        $this->addSql('DROP TABLE entity_test');
        $this->addSql('DROP TABLE messenger_messages');
    }
}

Solution

  • You are missing the actual definition of the parent (that we can see in your mappedBy attribute).

    You should add it like this:

    #[ORM\Entity(repositoryClass: EntityTestRepository::class)]
    class EntityTest
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(length: 255)]
        private ?string $name = null;
    
        #[ORM\OneToMany(targetEntity: EntityTest::class, mappedBy: 'entityTestFather')]
        private Collection $entityTests;
    
        #[ORM\ManyToOne(targetEntity: EntityTest::class, inversedBy: 'entityTests')]
        private EntityTest $entityTestFather;
    
        // don't forget to add new public getters/setters
    }