skip to Main Content

I’m trying to create a join table between two entities using Doctrine ORM and the Symfony maker bundle.

The two entities are User and Member. A User should be able to reference multiple Member entities, and Member entities can be referenced by multiple users.

User:

<?php

namespace AppEntity;

use AppRepositoryUserRepository;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineORMMapping as ORM;

#[ORMEntity(repositoryClass: UserRepository::class)]
class User
{
    public function __construct(
        #[ORMId]
        #[ORMColumn(type: 'string', length: 255)]
        public readonly string $username,

        #[ORMManyToMany(targetEntity: Member::class, mappedBy: 'name')]
        #[ORMJoinTable(
            name: 'tracked_members',
            joinColumns: [
                new ORMJoinColumn(name: 'user', referencedColumnName: 'username'),
            ],
            inverseJoinColumns: [
                new ORMJoinColumn(name: 'member', referencedColumnName: 'name'),
            ],
        )]
        public ArrayCollection $tracked_members,
    ) {
    }
}

Member:

<?php

namespace AppEntity;

use DoctrineORMMapping as ORM;
use AppRepositoryMemberRepository;

#[ORMEntity(repositoryClass: MemberRepository::class)]
class Member
{
    public function __construct(
        #[ORMId]
        #[ORMColumn(type: 'string', length: 255)]
        public readonly string $name,
    ) {
    }
}

However, when I run ./bin/console make:migration, I receive the following migration class with no warnings or errors indicating a problem:

<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use DoctrineDBALSchemaSchema;
use DoctrineMigrationsAbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20220705021447 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 member (name VARCHAR(255) NOT NULL, PRIMARY KEY(name))');
        $this->addSql('CREATE TABLE user (username VARCHAR(255) NOT NULL, PRIMARY KEY(username))');
    }

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

Why isn’t the SQL to create the join table tracked_users being added to the migration class?

Other relevant info:

Dependency Version
PHP 8.1.7
SQLite 3.37.0
doctrine/doctrine-migrations-bundle 3.2.2
doctrine/orm 2.13.3
symfony/maker-bundle 1.43.0

2

Answers


  1. Chosen as BEST ANSWER

    Finally got this to work. Even though PHP 8.1 supports nested attributes, and #[JoinTable] has joinColumns and inverseJoinColumns parameters, you must specify #[JoinColumn] and #[InverseJoinColumn] as top-level attributes.

    The following updated User entity generates the expected result:

    <?php
    
    namespace AppEntity;
    
    use AppRepositoryUserRepository;
    use DoctrineCommonCollectionsArrayCollection;
    use DoctrineORMMapping as ORM;
    
    #[ORMEntity(repositoryClass: UserRepository::class)]
    class User
    {
        public function __construct(
            #[ORMId]
            #[ORMColumn(type: 'string', length: 255)]
            public readonly string $username,
    
            #[ORMJoinColumn('username', referencedColumnName: 'username')]
            #[ORMInverseJoinColumn('member', referencedColumnName: 'name')]
            #[ORMJoinTable(name: 'tracked_members')]
            #[ORMManyToMany(targetEntity: Member::class)]
            public ArrayCollection $tracked_members,
        ) {
        }
    }
    

    Resulting Migration class:

    <?php
    
    declare(strict_types=1);
    
    namespace DoctrineMigrations;
    
    use DoctrineDBALSchemaSchema;
    use DoctrineMigrationsAbstractMigration;
    
    /**
     * Auto-generated Migration: Please modify to your needs!
     */
    final class Version20220705021447 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 member (name VARCHAR(255) NOT NULL, PRIMARY KEY(name))');
            $this->addSql('CREATE TABLE user (username VARCHAR(255) NOT NULL, PRIMARY KEY(username))');
            $this->addSql('CREATE TABLE tracked_members (username VARCHAR(255) NOT NULL, member VARCHAR(255) NOT NULL, PRIMARY KEY(username, member))');
            $this->addSql('CREATE INDEX IDX_CD2594E8F85E0677 ON tracked_members (username)');
            $this->addSql('CREATE INDEX IDX_CD2594E870E4FA78 ON tracked_members (member)');
        }
    
        public function down(Schema $schema): void
        {
            // this down() migration is auto-generated, please modify it to your needs
            $this->addSql('DROP TABLE member');
            $this->addSql('DROP TABLE user');
            $this->addSql('DROP TABLE tracked_members');
        }
    }
    

  2. You should add inversedBy.

    <?php
    
    namespace AppEntity;
    
    use DoctrineORMMapping as ORM;
    use AppRepositoryMemberRepository;
    use AppEntityUser;
    
    #[ORMEntity(repositoryClass: MemberRepository::class)]
    class Member
    {
        public function __construct(
            #[ORMId]
            #[ORMColumn(type: 'string', length: 255)]
            #[ORMManyToMany(targetEntity: User::class, inversedBy: 'tracked_members')] 
            public readonly string $name,
        ) {
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search