news 2026/9/24 17:26:31

Doctrine ORM 关联映射(Association Mapping)完全指南:从外键到对象引用的双向转换实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Doctrine ORM 关联映射(Association Mapping)完全指南:从外键到对象引用的双向转换实战
  • 数据库
  • ORM
  • 后端

【免费下载链接】orm

Doctrine Object Relational Mapper (ORM)

项目地址:https://gitcode.com/gh_mirrors/or/orm
点击查看免费下载

导读

本文是 Doctrine Object Relational Mapper (ORM) 官方参考文档中 association-mapping.rst 的深度展开版本,系统讲解如何在实体之间建立一对一(OneToOne)、一对多(OneToMany)、多对一(ManyToOne)、多对多(ManyToMany)关联,并覆盖单向/双向、自引用、拥有方(owning side)与反向方(inverse side)判定、映射默认值以及集合(Collection)初始化等核心议题。读完本文你将掌握:用属性(Attributes)或 XML 声明各类关联的完整写法、mappedBy/inversedBy/JoinColumn/JoinTable的正确语义、默认命名规则,以及如何避免最常见的关联陷阱。

核心思想:用对象引用取代外键

在使用 Doctrine ORM 时,你的业务代码中永远不需要直接操作外键,而是始终持有对目标对象的引用,Doctrine 会在内部把对象引用转换成数据库外键:

  • 单个对象的引用 → 表示为一条外键(foreign key);
  • 对象集合的引用 → 表示为多条指向持有该集合的对象的外键

因此本章的实用阅读技巧是:从左向右读关联名,左边的词指的是当前实体。例如:

关联含义
OneToMany当前实体的一个实例拥有被引用实体的多个实例
ManyToOne当前实体的多个实例引用被引用实体的一个实例
OneToOne当前实体的一个实例引用被引用实体的一个实例

如果关联只有一侧持有指向另一侧的属性,则该关联被称为**单向(unidirectional)关联;如果两侧都持有引用,则为双向(bidirectional)**关联。要完整理解关联,建议同时阅读文档 unitofwork-associations.rst 中关于拥有方与反向方的详细说明。

复合外键(Composite Foreign Keys):当目标实体拥有复合主键时,需要为复合主键的每一列分别声明一个 join column 映射,详见教程 composite-primary-keys.rst。

ManyToOne:单向关联(最常用)

多对一关联是对象间最常见的关联形态。典型例子:多个 User 拥有同一个 Address

使用 PHP 属性声明:

#[Entity] class User { // ... #[ManyToOne(targetEntity: Address::class)] #[JoinColumn(name: 'address_id', referencedColumnName: 'id')] private Address|null $address = null; } #[Entity] class Address { // ... }

等价的 XML 声明:

<doctrine-mapping> <entity name="User"> <many-to-one field="address" target-entity="Address"> <join-column name="address_id" referenced-column-name="id" /> </many-to-one> </entity> </doctrine-mapping>

注意:上面的#[JoinColumn]其实是可以省略的,因为默认值恰好就是address_idid#[ManyToOne]中的targetEntity也可以省略并默认推断为Address。省略细节见下文「映射默认值」小节。

生成的 MySQL Schema:

CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, address_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Address ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);

从源码看,#[ManyToOne]属性在 src/Mapping/ManyToOne.php 中声明了targetEntitycascadefetch(默认'LAZY')和inversedBy四个参数,其中targetEntityinversedBy均可为null(前者配合类型推断、后者用于双向关联时声明反向方)。

OneToOne:单向、双向与自引用

单向 OneToOne

一个Product实体引用一个Shipment实体:

#[Entity] class Product { // ... /** One Product has One Shipment. */ #[OneToOne(targetEntity: Shipment::class)] #[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')] private Shipment|null $shipment = null; // ... } #[Entity] class Shipment { // ... }

等价的 XML:

<doctrine-mapping> <entity class="Product"> <one-to-one field="shipment" target-entity="Shipment"> <join-column name="shipment_id" referenced-column-name="id" /> </one-to-one> </entity> </doctrine-mapping>

生成的 MySQL Schema(注意外键列上的UNIQUE 索引保证了“一对一”基数):

CREATE TABLE Product ( id INT AUTO_INCREMENT NOT NULL, shipment_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id), PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Shipment ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);

双向 OneToOne:mappedBy 与 inversedBy 首次登场

CustomerCart是一对一关系,Cart持有对Customer的反向引用,因此是双向的。这里首次出现mappedByinversedBy,它们用来告诉 Doctrine另一侧的哪个属性指向本对象

#[Entity] class Customer { // ... /** One Customer has One Cart. */ #[OneToOne(targetEntity: Cart::class, mappedBy: 'customer')] private Cart|null $cart = null; // ... } #[Entity] class Cart { // ... /** One Cart has One Customer. */ #[OneToOne(targetEntity: Customer::class, inversedBy: 'cart')] #[JoinColumn(name: 'customer_id', referencedColumnName: 'id')] private Customer|null $customer = null; // ... }

等价的 XML:

<doctrine-mapping> <entity name="Customer"> <one-to-one field="cart" target-entity="Cart" mapped-by="customer" /> </entity> <entity name="Cart"> <one-to-one field="customer" target-entity="Customer" inversed-by="cart"> <join-column name="customer_id" referenced-column-name="id" /> </one-to-one> </entity> </doctrine-mapping>

生成 Schema:

CREATE TABLE Cart ( id INT AUTO_INCREMENT NOT NULL, customer_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_BA388B79395C3F3 (customer_id), PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Customer ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);

关键结论:inversedBy放在哪一侧,哪一侧就是拥有方(owning side),并持有外键。本例中外键落在Cart表上。

自引用 OneToOne

实体也可以引用自身:

#[Entity] class Student { // ... /** One Student has One Mentor. */ #[OneToOne(targetEntity: Student::class)] #[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')] private Student|null $mentor = null; // ... }

生成 Schema:

CREATE TABLE Student ( id INT AUTO_INCREMENT NOT NULL, mentor_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Student ADD FOREIGN KEY (mentor_id) REFERENCES Student(id);

OneToMany:双向(标准形态)与带连接表的单向

双向 OneToMany:与 ManyToOne 是同一枚硬币的两面

一对多关联必须是双向的(除非使用连接表)。原因在于:一对多中“多”的一侧持有外键,是拥有方;Doctrine 必须看到“多”的一侧(ManyToOne)才能理解这个关联。这种双向映射要求在“一”侧使用mappedBy,在“多”侧使用inversedBy因此双向 OneToMany 与双向 ManyToOne 在本质上是同一个关联。

use Doctrine\Common\Collections\ArrayCollection; #[Entity] class Product { // ... /** * One product has many features. This is the inverse side. * @var Collection<int, Feature> */ #[OneToMany(targetEntity: Feature::class, mappedBy: 'product')] private Collection $features; // ... public function __construct() { $this->features = new ArrayCollection(); } } #[Entity] class Feature { // ... /** Many features have one product. This is the owning side. */ #[ManyToOne(targetEntity: Product::class, inversedBy: 'features')] #[JoinColumn(name: 'product_id', referencedColumnName: 'id')] private Product|null $product = null; // ... }

等价的 XML:

<doctrine-mapping> <entity name="Product"> <one-to-many field="features" target-entity="Feature" mapped-by="product" /> </entity> <entity name="Feature"> <many-to-one field="product" target-entity="Product" inversed-by="features"> <join-column name="product_id" referenced-column-name="id" /> </many-to-one> </entity> </doctrine-mapping>

生成 Schema(外键在Feature表上):

CREATE TABLE Product ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE Feature ( id INT AUTO_INCREMENT NOT NULL, product_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);

#[OneToMany]在 src/Mapping/OneToMany.php 中除了targetEntitymappedBy之外,还支持cascadefetch(默认'LAZY')、orphanRemoval以及indexBy#[ManyToOne]则支持inversedBycascadefetch

单向 OneToMany with Join Table

单向的一对多可以通过连接表映射。从 Doctrine 的角度看,它本质上就是单向多对多,只是在其中一个 join column 上加唯一约束来强制“一对多”的基数。下面的例子建立了UserPhonenumber的单向一对多:

#[Entity] class User { // ... /** * Many Users have Many Phonenumbers. * @var Collection<int, Phonenumber> */ #[JoinTable(name: 'users_phonenumbers')] #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] #[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)] #[ManyToMany(targetEntity: 'Phonenumber')] private Collection $phonenumbers; public function __construct() { $this->phonenumbers = new ArrayCollection(); } // ... } #[Entity] class Phonenumber { // ... }

等价的 XML:

<doctrine-mapping> <entity name="User"> <many-to-many field="phonenumbers" target-entity="Phonenumber"> <join-table name="users_phonenumbers"> <join-columns> <join-column name="user_id" referenced-column-name="id" /> </join-columns> <inverse-join-columns> <join-column name="phonenumber_id" referenced-column-name="id" unique="true" /> </inverse-join-columns> </join-table> </many-to-many> </entity> </doctrine-mapping>

生成 Schema(注意phonenumber_id上的唯一索引):

CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE users_phonenumbers ( user_id INT NOT NULL, phonenumber_id INT NOT NULL, UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), PRIMARY KEY(user_id, phonenumber_id) ) ENGINE = InnoDB; CREATE TABLE Phonenumber ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);

自引用 OneToMany:邻接表(Adjacency List)建模树形结构

通过自引用的一对多,可以建立Category对象的层级结构,这在数据库视角被称为邻接表方案:

#[Entity] class Category { // ... /** * One Category has Many Categories. * @var Collection<int, Category> */ #[OneToMany(targetEntity: Category::class, mappedBy: 'parent')] private Collection $children; /** Many Categories have One Category. */ #[ManyToOne(targetEntity: Category::class, inversedBy: 'children')] #[JoinColumn(name: 'parent_id', referencedColumnName: 'id')] private Category|null $parent = null; // ... public function __construct() { $this->children = new ArrayCollection(); } }

等价的 XML:

<doctrine-mapping> <entity name="Category"> <one-to-many field="children" target-entity="Category" mapped-by="parent" /> <many-to-one field="parent" target-entity="Category" inversed-by="children" /> </entity> </doctrine-mapping>

生成 Schema:

CREATE TABLE Category ( id INT AUTO_INCREMENT NOT NULL, parent_id INT DEFAULT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id);

ManyToMany:单向、双向与自引用

单向 ManyToMany

真正的多对多关联相对少见。下面是一个UserGroup之间的单向关联:

#[Entity] class User { // ... /** * Many Users have Many Groups. * @var Collection<int, Group> */ #[JoinTable(name: 'users_groups')] #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] #[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')] #[ManyToMany(targetEntity: Group::class)] private Collection $groups; // ... public function __construct() { $this->groups = new ArrayCollection(); } } #[Entity] class Group { // ... }

等价的 XML:

<doctrine-mapping> <entity name="User"> <many-to-many field="groups" target-entity="Group"> <join-table name="users_groups"> <join-columns> <join-column name="user_id" referenced-column-name="id" /> </join-columns> <inverse-join-columns> <join-column name="group_id" referenced-column-name="id" /> </inverse-join-columns> </join-table> </many-to-many> </entity> </doctrine-mapping>

生成 Schema(连接表主键由两列共同构成):

CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE users_groups ( user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id) ) ENGINE = InnoDB; CREATE TABLE Group ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id);

为什么多对多不常用?因为通常你想为关联本身附加额外的属性,这时应该引入一个“关联类(association class)”,于是直接的多对多会消失,被三个参与类之间的 OneToMany/ManyToOne 所取代。

连接表管理(Join Table Management):对于多对多关联,ORM 负责管理连接表中连接两侧的行。由于它处理实体删除的方式,数据库层面的约束可能不会像直觉那样工作,因此务必阅读 unitofwork-associations.rst 中关于多对多连接表删除处理的章节。

双向 ManyToMany

与上面相似,只是这次是双向的:

#[Entity] class User { // ... /** * Many Users have Many Groups. * @var Collection<int, Group> */ #[ManyToMany(targetEntity: Group::class, inversedBy: 'users')] #[JoinTable(name: 'users_groups')] private Collection $groups; public function __construct() { $this->groups = new ArrayCollection(); } // ... } #[Entity] class Group { // ... /** * Many Groups have Many Users. * @var Collection<int, User> */ #[ManyToMany(targetEntity: User::class, mappedBy: 'groups')] private Collection $users; public function __construct() { $this->users = new ArrayCollection(); } // ... }

等价的 XML:

<doctrine-mapping> <entity name="User"> <many-to-many field="groups" inversed-by="users" target-entity="Group"> <join-table name="users_groups"> <join-columns> <join-column name="user_id" referenced-column-name="id" /> </join-columns> <inverse-join-columns> <join-column name="group_id" referenced-column-name="id" /> </inverse-join-columns> </join-table> </many-to-many> </entity> <entity name="Group"> <many-to-many field="users" mapped-by="groups" target-entity="User"/> </entity> </doctrine-mapping>

生成的 MySQL Schema 与上面单向多对多完全相同——双向与单向的数据库结构并无区别,区别只存在于 ORM 层的同步维护逻辑。

如何选择 ManyToMany 的拥有方与反向方

对于多对多关联,你可以选择哪个实体作为拥有方、哪个作为反向方。有一个非常简单的语义规则:问自己哪个实体负责连接管理,就把哪个实体作为拥有方。

ArticleTag为例:每当你想把一篇文章与标签(或反之)连接起来时,通常是 Article 承担这个职责——新建文章时把已有或新建的标签挂上去,“创建文章”的表单也往往直接支持指定标签。因此把 Article 作为拥有方会让代码更易理解:

class Article { private Collection $tags; public function addTag(Tag $tag): void { $tag->addArticle($this); // synchronously updating inverse side $this->tags[] = $tag; } } class Tag { private Collection $articles; public function addArticle(Article $article): void { $this->articles[] = $article; } }

这样可以在 Article 一侧统一完成标签的添加:

$article = new Article(); $article->addTag($tagA); $article->addTag($tagB);

注意addTag()中“同步更新反向方”的习惯做法——手动维护双向关联两侧的集合一致性,是避免脏检查遗漏的常用技巧。

自引用 ManyToMany:好友关系

自引用多对多也很常见,典型场景是“用户的好友”,目标实体仍是User。下面这个例子是双向的,User同时拥有$friendsWithMe(反向方,mappedBy: 'myFriends')和$myFriends(拥有方,声明JoinTable与两条JoinColumn):

#[Entity] class User { // ... /** * Many Users have Many Users. * @var Collection<int, User> */ #[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')] private Collection $friendsWithMe; /** * Many Users have many Users. * @var Collection<int, User> */ #[JoinTable(name: 'friends')] #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] #[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')] #[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')] private Collection $myFriends; public function __construct() { $this->friendsWithMe = new ArrayCollection(); $this->myFriends = new ArrayCollection(); } // ... }

生成 Schema:

CREATE TABLE User ( id INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(id) ) ENGINE = InnoDB; CREATE TABLE friends ( user_id INT NOT NULL, friend_user_id INT NOT NULL, PRIMARY KEY(user_id, friend_user_id) ) ENGINE = InnoDB; ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id); ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id);

映射默认值:让映射代码缩减到最少

@JoinColumn@JoinTable定义通常都是可选的,它们有合理的默认值。一对一/多对一关联中 join column 的默认值为:

name: "<fieldname>_id" referencedColumnName: "id"

例如下面这个最简映射:

#[OneToOne(targetEntity: Shipment::class)] private Shipment|null $shipment = null;

等价于更啰嗦的完整写法:

/** One Product has One Shipment. */ #[OneToOne(targetEntity: Shipment::class)] #[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')] private Shipment|null $shipment = null;

XML 版本同样如此:

<doctrine-mapping> <entity class="Product"> <one-to-one field="shipment" target-entity="Shipment" /> </entity> </doctrine-mapping>

等价于:

<doctrine-mapping> <entity class="Product"> <one-to-one field="shipment" target-entity="Shipment"> <join-column name="shipment_id" referenced-column-name="id" /> </one-to-one> </entity> </doctrine-mapping>

@JoinTable(用于多对多)也有类似的默认值。考虑这个最简映射:

class User { // ... /** @var Collection<int, Group> */ #[ManyToMany(targetEntity: Group::class)] private Collection $groups; // ... }

它等价于:

class User { // ... /** * Many Users have Many Groups. * @var Collection<int, Group> */ #[JoinTable(name: 'User_Group')] #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] #[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')] #[ManyToMany(targetEntity: Group::class)] private Collection $groups; // ... }

默认值规则总结:

  • 连接表名:默认取参与关联的两个类简单类名(不带命名空间)用下划线连接,例如UserGroup得到User_Group
  • join column 名:默认取目标类的简单类名后加_id,例如目标类为Group则列名为group_id
  • referencedColumnName:始终默认为id,与一对一/多对一一致。

类型化属性(Typed Properties)下的极简映射:使用 Doctrine 2.9 或更新版本时,ManyToOneOneToOne关联中的targetEntity可以省略,ORM 会根据属性的类型声明自动推断。于是:

#[OneToOne] private Shipment $shipment;

等价于完整写法:

/** One Product has One Shipment. */ #[OneToOne(targetEntity: Shipment::class)] #[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')] private Shipment $shipment;

(传统注解写法等价于@OneToOne(targetEntity="Shipment") @JoinColumn(name="shipment_id", referencedColumnName="id")。)

接受这些默认值,即可把映射代码压缩到最少。

JoinColumn 的完整参数面

从源码 src/Mapping/JoinColumnProperties.php 可以看到,JoinColumn/InverseJoinColumnnamereferencedColumnName外还支持:

参数说明
deferrable是否可延迟(默认false
unique是否唯一(单向 OneToMany via join table 中用于强制基数)
nullable是否允许为 NULL(默认为null,即交由映射推断)
onDelete外键的 ON DELETE 动作(如CASCADE
columnDefinition直接指定列定义 DDL
fieldName关联的字段名
foreignKeyName外键约束名称
options平台相关选项数组

而 src/Mapping/JoinTable.php 中JoinTable还支持schemajoinColumnsinverseJoinColumnsforeignKeyNameinverseForeignKeyNameoptionsAssociationMapping基类(src/Mapping/AssociationMapping.php)则统一承载了cascadepersist/remove/detach/refresh/all)与fetch(默认LAZY)等横切属性。

集合(Collections):多值关联的必备类型

很遗憾,PHP 数组虽然在很多场景下很好用,却缺少使其适合 ORM 场景下懒加载(lazy loading)的特性。因此本手册中所有多值关联的例子都使用Collection接口及其默认实现ArrayCollection,两者定义在Doctrine\Common\Collections命名空间中。Collection实现了 PHP 的ArrayAccessTraversableCountable接口。

重要说明Collection接口与ArrayCollection类,和 Doctrine 命名空间中的其他东西一样,既不属于 ORM 也不属于 DBAL——它是一个纯粹的 PHP 类,除了 PHP 本身(以及 SPL)之外没有任何外部依赖。因此在你的模型和其他地方使用它,不会引入对 ORM 的耦合

初始化集合:在构造函数中完成

你应该始终在实体的构造函数中初始化@OneToMany@ManyToMany关联的集合

use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; #[Entity] class User { /** Many Users have Many Groups. */ #[ManyToMany(targetEntity: Group::class)] private Collection $groups; public function __construct() { $this->groups = new ArrayCollection(); } public function getGroups(): Collection { return $this->groups; } }

这样,即使实体尚未与任何EntityManager关联,下面的代码也能正常工作:

$group = new Group(); $user = new User(); $user->getGroups()->add($group);

集合的继承语义

ArrayCollectionadd()会返回布尔值以指示是否发生变更,且集合变更会被持久化层的变更追踪机制(Change Tracking)捕获。这也解释了为什么上述“同步更新双向关联两侧”的辅助方法(如Article::addTag())非常关键:只修改反向方集合而不修改拥有方,可能导致脏检查漏判。

小结与最佳实践

  1. 对象引用即外键:业务代码永远不碰外键,Doctrine 负责转换;左读右记:OneToMany/ManyToOne/OneToOne都以当前实体为视角。
  2. 双向关联必须成对声明:一侧mappedBy、另一侧inversedBy;拥有方持有外键(OneToOne/OneToMany 中就是声明JoinColumn的那一侧),inversedBy落在哪一侧,哪一侧就是拥有方。
  3. OneToMany 默认必须双向:除非通过连接表 + 唯一约束模拟单向一对多。
  4. 多对多尽量考虑关联类:需要为关联附加属性时,用三个类之间的 OneToMany/ManyToOne 取代直接多对多。
  5. 接受默认值<field>_id/id/Class_Class这些默认规则能让映射代码极简;Doctrine 2.9+ 的类型化属性还能省去targetEntity
  6. 集合在构造函数初始化:使用Collection/ArrayCollection,与 ORM 解耦且天然支持懒加载。

如需深入了解拥有方/反向方在持久化与删除时的具体行为(尤其是多对多连接表的删除语义),请继续阅读 unitofwork-associations.rst;复合主键下的关联映射细节见 composite-primary-keys.rst。关联映射相关的全部 PHP 属性类(ManyToOneOneToOneOneToManyManyToManyJoinColumnJoinTableInverseJoinColumn等)均可在 src/Mapping 目录中查看,测试样例可参考 tests/Tests/Models 下的模型目录。

  • 数据库
  • ORM
  • 后端

【免费下载链接】orm

Doctrine Object Relational Mapper (ORM)

项目地址:https://gitcode.com/gh_mirrors/or/orm
点击查看免费下载
上一篇:Trezor Firmware Monorepo完全指南:从架构到安全的终极探索
下一篇:CircuitVerse 开源项目常见问题解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 17:25:47

第27篇-需求获取技术-面向六类干系人的实战方法

【软考高级系统分析师全链路通关实战】第 27 篇&#xff1a;需求获取技术——面向六类干系人的实战方法 本系列定位&#xff1a;面向有开发经验、从零备考软考高级「系统分析师」的工程师&#xff0c;以《系统分析师教程&#xff08;第 2 版&#xff09;》为主线&#xff0c;按…

作者头像 李华
网站建设 2026/9/24 17:23:36

ComfyUI-WanVideoWrapper:从 0 到 1 跑通文生视频与图生视频

ComfyUI-WanVideoWrapper&#xff1a;从 0 到 1 跑通文生视频与图生视频 【免费下载链接】ComfyUI-WanVideoWrapper 项目地址: https://gitcode.com/GitHub_Trending/co/ComfyUI-WanVideoWrapper ComfyUI-WanVideoWrapper 是一套 ComfyUI 自定义节点。克隆进 custom_no…

作者头像 李华
网站建设 2026/9/24 17:23:15

Linux运维踩坑实录:压缩文件夹报错“zip error: Nothing to do!”

Linux运维踩坑实录&#xff1a;压缩文件夹报错“zip error: Nothing to do!”的深度剖析与最佳实践 引言&#xff1a;文件打包&#xff0c;运维与开发的必经之路 在当今的软件开发和系统运维领域&#xff0c;Linux 操作系统凭借其卓越的稳定性和强大的命令行工具生态&#xff…

作者头像 李华
网站建设 2026/9/24 17:22:32

StoryDiffusion AI漫画生成指南:三步跑通出整套角色一致漫画

StoryDiffusion AI漫画生成指南&#xff1a;三步跑通出整套角色一致漫画 【免费下载链接】StoryDiffusion Accepted as [NeurIPS 2024] Spotlight Presentation Paper 项目地址: https://gitcode.com/GitHub_Trending/st/StoryDiffusion StoryDiffusion是一款开源的AI漫…

作者头像 李华