phpsymfonydoctrine-ormdoctrine-migrations

Doctrine Migrations not creating join table


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 App\Entity;

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

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
    public function __construct(
        #[ORM\Id]
        #[ORM\Column(type: 'string', length: 255)]
        public readonly string $username,

        #[ORM\ManyToMany(targetEntity: Member::class, mappedBy: 'name')]
        #[ORM\JoinTable(
            name: 'tracked_members',
            joinColumns: [
                new ORM\JoinColumn(name: 'user', referencedColumnName: 'username'),
            ],
            inverseJoinColumns: [
                new ORM\JoinColumn(name: 'member', referencedColumnName: 'name'),
            ],
        )]
        public ArrayCollection $tracked_members,
    ) {
    }
}

Member:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Repository\MemberRepository;

#[ORM\Entity(repositoryClass: MemberRepository::class)]
class Member
{
    public function __construct(
        #[ORM\Id]
        #[ORM\Column(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 Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * 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

Solution

  • 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 App\Entity;
    
    use App\Repository\UserRepository;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity(repositoryClass: UserRepository::class)]
    class User
    {
        public function __construct(
            #[ORM\Id]
            #[ORM\Column(type: 'string', length: 255)]
            public readonly string $username,
    
            #[ORM\JoinColumn('username', referencedColumnName: 'username')]
            #[ORM\InverseJoinColumn('member', referencedColumnName: 'name')]
            #[ORM\JoinTable(name: 'tracked_members')]
            #[ORM\ManyToMany(targetEntity: Member::class)]
            public ArrayCollection $tracked_members,
        ) {
        }
    }
    

    Resulting Migration class:

    <?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 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');
        }
    }