如何用Doctrine生成指定MySQL Schema?关联映射遇问题求助
问题描述
我想知道是否可以用Doctrine生成以下MySQL Schema:
目标表结构
languages表
CREATE TABLE `languages` ( `id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `status` INT(1) NOT NULL DEFAULT '0', `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
languages_content表
CREATE TABLE `languages_content` ( `language_id` int(4) UNSIGNED NOT NULL, `language` int(4) UNSIGNED NOT NULL, `label` varchar(50) NOT NULL, PRIMARY KEY (`language_id`,`language`), KEY `language` (`language`), CONSTRAINT `languages_content_idfk_1` FOREIGN KEY (`language_id`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `languages_content_idfk_2` FOREIGN KEY (`language`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这个languages_content表用于关联语言表中的翻译与语言。
尝试过的方案及问题
- 多对多关联:使用Doctrine的多对多注解后,生成的
languages_content表会丢失label字段,代码如下:
/** * Languages * * @ORM\Table(name="languages") * @ORM\Entity */ class Languages { /** * @var int * * @ORM\Column(name="id", type="integer", nullable=false, options={"unsigned"=true}) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=50, nullable=false) */ private $name; /** * @var int * * @ORM\Column(name="status", type="integer", nullable=false) */ private $status = '0'; /** * @var \DateTime * * @ORM\Column(name="created_at", type="datetime", nullable=false) */ private $createdAt; /** * @var \Doctrine\Common\Collections\Collection * * @ORM\ManyToMany(targetEntity="Languages", inversedBy="language") * @ORM\JoinTable(name="languages_content", * joinColumns={ * @ORM\JoinColumn(name="language_id", referencedColumnName="id") * }, * inverseJoinColumns={ * @ORM\JoinColumn(name="language", referencedColumnName="id") * } * ) */ private $language = array();
- 多对一关联:改用一对多/多对一关联后,
languages_content表会被自动添加一个原本不存在的自增id字段,代码如下:
#[ORM\Entity(repositoryClass: LanguagesRepository::class)] class Languages { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 50, nullable: false)] private ?string $name = null; #[ORM\Column(nullable: true)] private ?int $status = 0; #[ORM\Column(nullable: true)] private ?\DateTimeImmutable $createdAt = null; #[ORM\OneToMany(mappedBy: 'languages', targetEntity: LanguagesContent::class)] private Collection $language;
想请教如何用Doctrine生成符合要求的Schema,提前感谢帮助或建议。
解决方案
完全可以生成,问题出在关联方式的选择和配置细节上:
为什么之前的方案不行?
- 原生多对多:Doctrine自带的多对多只支持纯关联表(仅包含两个外键),一旦关联表需要额外字段(比如你的
label),就必须把关联表单独定义为实体,不能再用原生多对多注解。 - 普通一对多:直接用一对多关联时,Doctrine默认会给关联实体添加自增主键,但你需要的是
language_id+language的复合主键,所以得手动配置主键规则。
正确实现步骤
1. 创建LanguagesContent实体
把关联表做成独立实体,指定复合主键,同时配置两个外键关联到Languages:
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] #[ORM\Table(name: 'languages_content')] #[ORM\IdJoinColumns([ new ORM\IdJoinColumn(name: 'language_id', referencedColumnName: 'id'), new ORM\IdJoinColumn(name: 'language', referencedColumnName: 'id') ])] class LanguagesContent { #[ORM\ManyToOne(targetEntity: Languages::class, inversedBy: 'translations')] #[ORM\JoinColumn(name: 'language_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] private ?Languages $originalLanguage = null; #[ORM\ManyToOne(targetEntity: Languages::class)] #[ORM\JoinColumn(name: 'language', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] private ?Languages $targetLanguage = null; #[ORM\Column(type: 'string', length: 50, nullable: false)] private ?string $label = null; // 构造函数 public function __construct(Languages $original, Languages $target) { $this->originalLanguage = $original; $this->targetLanguage = $target; } // 省略getter、setter方法 }
2. 修改Languages实体的关联配置
把原来的多对多改成一对多关联到LanguagesContent:
<?php namespace App\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: LanguagesRepository::class)] #[ORM\Table(name: 'languages')] class Languages { #[ORM\Id] #[ORM\GeneratedValue(strategy: 'IDENTITY')] #[ORM\Column(type: 'integer', options: ['unsigned' => true])] private ?int $id = null; #[ORM\Column(type: 'string', length: 50, nullable: false)] private ?string $name = null; #[ORM\Column(type: 'integer', nullable: false, options: ['default' => 0])] private int $status = 0; #[ORM\Column(type: 'datetime', nullable: false)] private ?\DateTimeInterface $createdAt = null; #[ORM\OneToMany(mappedBy: 'originalLanguage', targetEntity: LanguagesContent::class, cascade: ['persist', 'remove'])] private Collection $translations; public function __construct() { $this->translations = new ArrayCollection(); } // 省略其他getter、setter方法 public function addTranslation(LanguagesContent $translation): self { if (!$this->translations->contains($translation)) { $this->translations->add($translation); } return $this; } public function removeTranslation(LanguagesContent $translation): self { $this->translations->removeElement($translation); return $this; } }
3. 关键配置说明
- 复合主键:通过
#[ORM\IdJoinColumns]指定language_id和language作为联合主键,避免Doctrine自动添加自增id。 - 关联关系:
Languages与LanguagesContent是一对多关系,LanguagesContent与Languages是两个多对一关系(分别对应原始语言和目标语言)。 - 级联操作:配置
onDelete: 'CASCADE'和cascade: ['persist', 'remove'],与你原始SQL中的约束规则保持一致。
配置完成后,运行doctrine:schema:update --force就能生成完全符合要求的表结构。
内容的提问来源于stack exchange,提问作者Ludwig Schmidt
相关产品推荐
相关产品推荐

