ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hyperf 模型关联(Model Relationships)完全指南:从 hasOne 到多态关联与预加载

Hyperf 模型关联(Model Relationships)完全指南:从 hasOne 到多态关联与预加载 后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载本文是 Hyperf 框架数据库组件中模型关联Model Relationships的完整实战指南。Hyperf 的模型基于Hyperf\Database\Model\Model构建其关联系统沿用了成熟 ORM 的关系建模思想覆盖一对一hasOne、一对多hasMany、反向关联belongsTo、多对多belongsToMany以及多态关联Polymorphic等全部常见关系类型并内置了 N 1 查询问题的预加载Eager Loading解决方案。读完本文你将掌握在 Hyperf 中定义各类模型关联、通过动态属性与方法链查询关联数据、操作多对多中间表、自定义多态类型映射以及使用whereHasMorph进行多态存在性查询的完整能力。在 Hyperf 中定义模型关联在 Hyperf 中模型关联Relationships以方法的形式定义在模型类中。与模型本身一样每个关联方法本质上都是一个强大的查询构建器Query Builder支持方法链method chaining式的连续调用。例如我们可以在role关联的查询链上追加约束条件$user-role()-where(level, 1)-get();之所以能做到这一点是因为关联方法返回的不是普通的数据集合而是一个Hyperf\Database\Model\Relations\Relation及其子类的实例如HasOne、HasMany、BelongsTo、BelongsToMany、MorphOne、MorphTo等这些类都位于 src/database/src/Model/Relations 目录下。这些关联对象持有相关的查询构建器允许你继续叠加where、orderBy、limit等约束最终再通过get()、first()等方法获取结果。所有关联的定义入口方法hasOne、hasMany、belongsTo、belongsToMany、morphOne、morphMany、morphTo、morphToMany等都由模型基类通过 HasRelationships.php 提供业务模型只需继承Hyperf\DbConnection\Model\Model即可直接使用。模型基类的选择Hyperf 项目中使用DbConnection组件时模型应继承自Hyperf\DbConnection\Model\Model即 src/db-connection/src/Model/Model.php 中定义的类它扩展了Hyperf\Database\Model\Model并额外注入了容器访问HasContainer与仓储访问HasRepository能力能够无缝接入 Hyperf 的依赖注入容器和连接池体系。本文后续示例均基于该类。One To One一对一关联一对一one-to-one是最基础的关联形态。例如User模型与Role模型一一对应。定义方式是在User模型中编写role方法在方法内调用hasOne并返回其结果?php declare(strict_types1); namespace App\Models; use Hyperf\DbConnection\Model\Model; class User extends Model { public function role() { return $this-hasOne(Role::class, user_id, id); } }hasOne方法的签名见 HasRelationships.php为public function hasOne($related, $foreignKey null, $localKey null)第一个参数$related关联模型related model的类名第二个参数$foreignKey关联模型表上的外键字段名第三个参数$localKey当前模型表上的本地键字段名。当省略$foreignKey时框架会调用$this-getForeignKey()自动推断其规则是将当前模型类名转换为 snake_case 并追加_id后缀例如User模型对应user_id省略$localKey时默认使用当前模型的主键getKeyName()。定义好关联后就可以利用 Hyperf 的动态属性Dynamic Attributes来获取关联数据——把关联方法当作模型属性一样访问$role User::query()-find(1)-role;动态属性的实现链路为模型类重载了魔术方法__get见 src/database/src/Model/Model.php#L144-L147它委托给getAttributegetAttribute见 HasAttributes.php#L194-L217在确认该键不是普通字段属性、也没有对应的 get 访问器之后将其交给getRelationValue最终由getRelationshipFromMethod见 HasAttributes.php#L871-L894调用同名方法并校验返回值必须是Relation实例——如果关联方法忘记写return返回了null或返回了非关联对象会抛出LogicException并给出明确的错误提示Was the return keyword used?这在实际开发中非常有助于快速定位问题。One To Many一对多关联一对多one-to-many用于表达「一个模型拥有多个相关模型」的关系。例如一位作者User可以撰写多本书Book?php declare(strict_types1); namespace App\Models; use Hyperf\DbConnection\Model\Model; class User extends Model { public function books() { return $this-hasMany(Book::class, user_id, id); } }外键命名约定Hyperf 会自动推断Book表上的外键字段名。按照约定外键采用「属主模型名的 snake_case 形式 _id后缀」因此上例中User关联到Book的外键就是user_id。hasMany的签名与默认值推断逻辑与hasOne完全一致见 HasRelationships.php 中hasMany的实现。访问一对多关联数据定义完成后通过动态属性books即可获得书的集合Collection$books User::query()-find(1)-books; foreach ($books as $book) { // }同样地由于关联本质是查询构建器可以对其追加约束并配合终结方法使用$book User::query()-find(1)-books()-where(title, Mastering Hyperf Framework in One Month)-first();One To Many (Inverse)反向关联 belongsTo既然已经能从作者获取其全部作品自然也需要能从一本书反向找到它的作者。这个关联是hasMany的逆操作需要在子模型Book上使用belongsTo定义?php declare(strict_types1); namespace App\Models; use Hyperf\DbConnection\Model\Model; class Book extends Model { public function author() { return $this-belongsTo(User::class, user_id, id); } }belongsTo的签名见 HasRelationships.php为public function belongsTo($related, $foreignKey null, $ownerKey null, $relation null)当省略$foreignKey时框架会借助 PHP 的 debug backtrace 推断出当前关联方法名这里即author再按「snake(方法名) _ 关联模型主键名」的规则推导外键例如方法名author对应外键author_id。省略$ownerKey时默认使用关联模型的主键。定义完成后通过动态属性author即可拿到对应的User模型$book Book::find(1); echo $book-author-name;Many To Many多对多关联多对多many-to-many比hasOne/hasMany稍复杂。例如一个用户可以拥有多个角色而同一角色如 Administrator也可以被多个用户共享。此时需要三张数据表users、roles以及中间表join tablerole_user。中间表的命名基于两个关联模型名的字母顺序拼接roleuserrole_user表中包含user_id和role_id两个外键字段。多对多关联通过调用belongsToMany定义。例如在User模型中定义roles方法?php namespace App; use Hyperf\DbConnection\Model\Model; class User extends Model { public function roles() { return $this-belongsToMany(Role::class); } }定义完成后通过动态属性roles获取用户的角色集合$user User::query()-find(1); foreach ($user-roles as $role) { // }同样地roles方法也可以作为查询构建器使用并通过方法链追加约束$roles User::find(1)-roles()-orderBy(name)-get();自定义中间表与键名如前述默认情况下 Hyperf 会按两个关联模型名的字母顺序拼接出中间表名。你也可以通过belongsToMany的第二个参数显式指定return $this-belongsToMany(Role::class, role_user);belongsToMany的完整签名见 HasRelationships.php支持更多自定义项public function belongsToMany( $related, // 关联模型类名 $table null, // 中间表名 $foreignPivotKey null, // 定义方在中间表中的外键名 $relatedPivotKey null, // 关联方在中间表中的外键名 $parentKey null, // 定义方本地键 $relatedKey null, // 关联方本地键 $relation null // 关联名默认从方法名推断 )其中第三个参数是定义关联的模型在中间表中的外键名如user_id第四个参数是另一模型在中间表中的外键名如role_idreturn $this-belongsToMany(Role::class, role_user, user_id, role_id);访问中间表字段pivot多对多关联依赖中间表存储关联元信息。Hyperf 提供了一系列方法用于操作这张表。例如User关联多个Role取回关联对象后可通过每个Role模型上的pivot属性访问中间表数据$user User::find(1); foreach ($user-roles as $role) { echo $role-pivot-created_at; }需要注意每个取回的Role模型都会被自动附加一个pivot属性它代表中间表对应的模型对象对应 Pivot.php 中的Pivot类用法与普通 Hyperf 模型一致。默认情况下pivot对象只包含两个关联模型的主键。如果中间表还有其他业务字段必须在定义关联时显式声明withPivotreturn $this-belongsToMany(Role::class)-withPivot(column1, column2);如果希望中间表自动维护created_at与updated_at时间戳追加withTimestamps即可return $this-belongsToMany(Role::class)-withTimestamps();自定义pivot属性名中间表数据默认通过pivot属性访问但你完全可以按业务语义重命名它。例如应用中有「用户订阅播客」的多对多关系可以把访问器从pivot改为subscription使用as方法return $this-belongsToMany(Podcast::class)-as(subscription)-withTimestamps();定义后即可通过自定义名称访问中间表数据$users User::with(podcasts)-get(); foreach ($users-flatMap-podcasts as $podcast) { echo $podcast-subscription-created_at; }通过中间表过滤关联定义多对多关联时还可以使用wherePivot与wherePivotIn直接对belongsToMany返回的结果进行过滤return $this-belongsToMany(App\Role)-wherePivot(approved, 1); return $this-belongsToMany(App\Role)-wherePivotIn(priority, [1, 2]);wherePivot用于等值过滤中间表字段wherePivotIn用于IN集合过滤它们与withPivot、withTimestamps、as一样都是在 BelongsToMany.php 上提供的中间表交互能力。Eager Loading预加载与 N 1 查询将 Hyperf 关联作为属性访问时关联数据是懒加载lazy load的——也就是说关联数据只有在属性第一次被访问时才真正发起查询。而 Hyperf 同时支持在查询父模型时预加载eager load子关联用于消除 N 1 查询问题。先看 N 1 问题的典型场景。假设User与Role是一对一关联?php declare(strict_types1); namespace App\Models; use Hyperf\DbConnection\Model\Model; class User extends Model { public function role() { return $this-hasOne(Role::class, user_id, id); } }现在取出所有用户及其角色$users User::query()-get(); foreach ($users as $user){ echo $user-role-name; }这个循环会执行1 次查询取出所有用户然后为每一个用户再各执行 1 次查询取出其角色。如果有 10 个用户就会产生 11 条查询1 条查user表另外 10 条分别查role表。这就是典型的 N 1 问题。幸运的是预加载可以把整个流程压缩到仅 2 条查询。查询时通过with方法声明需要预加载的关联$users User::query()-with(role)-get(); foreach ($users as $user){ echo $user-role-name; }此时框架只执行两条 SQLSELECT * FROM user; SELECT * FROM role WHERE id in (1, 2, 3, ...);with方法内部会收集所有父模型的主键值构造WHERE id IN (...)一次性取出全部关联数据再按主键关系挂载到各个父模型上relationLoaded、setRelation等挂载逻辑见 HasRelationships.php最终在访问$user-role时直接命中已加载的内存数据不再发起额外查询。Polymorphic Relationships多态关联多态关联Polymorphic Relationships允许一个目标模型通过同一条关联被多个不同类型的模型所拥有。在 Hyperf 中实现方式与标准 ORM 一致。One To One (Polymorphic)多态一对一多态一对一与普通一对一类似区别在于目标模型可以被多种模型类型共享。例如Book和User共用Image模型利用多态一对一一张图片表可以同时服务于书籍和用户。表结构book id - integer title - string user id - integer name - string image id - integer url - string imageable_id - integer imageable_type - stringimage表中的imageable_id字段根据不同的imageable_type具有不同的含义。默认情况下imageable_type直接存储关联模型的类名字符串如App\Model\User。模型示例?php namespace App\Model; class Image extends Model { public function imageable() { return $this-morphTo(); } } class Book extends Model { public function image() { return $this-morphOne(Image::class, imageable); } } class User extends Model { public function image() { return $this-morphOne(Image::class, imageable); } }morphOne($related, $name, $type null, $id null, $localKey null)的第二个参数$name是多态关联名即多态字段前缀。实现中会通过getMorphs($name, $type, $id)生成{name}_type与{name}_id两个字段名对应上表的imageable_type与imageable_id并据此构造查询条件见 HasRelationships.php 中morphOne的实现。获取关联数据按上述模型定义即可通过关联获取对应数据。例如获取某个用户的图片use App\Model\User; $user User::find(1); $image $user-image;也可以反向获取某张图片对应的用户或书籍——imageable会根据imageable_type自动实例化对应的User或Bookuse App\Model\Image; $image Image::find(1); $imageable $image-imageable;One To Many (Polymorphic)多态一对多模型示例?php namespace App\Model; class Image extends Model { public function imageable() { return $this-morphTo(); } } class Book extends Model { public function images() { return $this-morphMany(Image::class, imageable); } } class User extends Model { public function images() { return $this-morphMany(Image::class, imageable); } }获取关联数据获取某个用户的全部图片use App\Model\User; $user User::query()-find(1); foreach ($user-images as $image) { // ... }Custom Polymorphic Mapping自定义多态类型映射默认情况下框架要求type字段如imageable_type存储对应的模型类名即User::class、Book::class这在实际应用中会带来不便——数据库里直接写入完整类名既冗长又让数据库与应用的内部结构强耦合。为此Hyperf 提供了自定义映射机制使用Relation::morphMap将别名与模型类对应起来从而把数据库结构与应用内部结构解耦use App\Model; use Hyperf\Database\Model\Relations\Relation; Relation::morphMap([ user Model\User::class, book Model\Book::class, ]);morphMap的实现见 Relation.php#L329-L339支持传入$map数组与$merge合并标志默认true即与已有映射合并而非覆盖定义后框架通过getMorphedModel($alias)见 Relation.php#L347-L350将存储的别名如user解析回模型类从而正确加载关联。由于Relation::morphMap修改后驻留在内存中静态属性因此需要在项目启动时完成映射注册。推荐做法是注册一个监听BootApplication事件的 Listener?php declare(strict_types1); /** * This file is part of Hyperf. * * link https://www.hyperf.io * document https://doc.hyperf.io * contact grouphyperf.io * license https://github.com/hyperf/hyperf/blob/master/LICENSE */ namespace App\Listener; use App\Model; use Hyperf\Database\Model\Relations\Relation; use Hyperf\Event\Annotation\Listener; use Hyperf\Event\Contract\ListenerInterface; use Hyperf\Framework\Event\BootApplication; #[Listener] class MorphMapRelationListener implements ListenerInterface { public function listen(): array { return [ BootApplication::class, ]; } public function process(object $event) { Relation::morphMap([ user Model\User::class, book Model\Book::class, ]); } }该 Listener 通过#[Listener]注解注册由Hyperf\Event组件驱动在应用启动BootApplication事件时执行映射注册从而保证整个生命周期内所有多态关联都能正确解析别名。Nested Eager LoadingmorphToRelationships嵌套预加载多态关联如果希望预加载morphTo关联并同时预加载该关联可能返回的各种实体上的嵌套关联可以将with与morphTo关联的morphWith方法结合使用。例如我们希望从一张图片预加载book.user书籍及其作者两层关联use App\Model\Book; use App\Model\Image; use Hyperf\Database\Model\Relations\MorphTo; $images Image::query()-with([ imageable function (MorphTo $morphTo) { $morphTo-morphWith([ Book::class [user], ]); }, ])-get();对应的 SQL 查询序列如下-- Query semua images select * from images; -- Query daftar user yang sesuai dengan images select * from user where user.id in (1, 2); -- Query daftar buku yang sesuai dengan images select * from book where book.id in (1, 2, 3); -- Query daftar user yang sesuai dengan daftar buku select * from user where user.id in (1, 2);即先查询全部图片再根据图片中记录的imageable_type分别批量查询各类型对应的实体这里是user与book最后对book实体上的user嵌套关联再执行一次批量预加载。整个过程共 4 条 SQL避免了逐条访问时的 N 1 膨胀。Polymorphic Relationship Query多态关联存在性查询要按MorphTo关联的「存在性」过滤查询可以使用whereHasMorph方法及其配套方法。以下示例查询「所属实体book 或 user的 ID 为 1」的图片列表use App\Model\Book; use App\Model\Image; use App\Model\User; use Hyperf\Database\Model\Builder; $images Image::query()-whereHasMorph( imageable, [ User::class, Book::class, ], function (Builder $query) { $query-where(imageable_id, 1); } )-get();whereHasMorph接收三个参数多态关联名、允许的模型类列表以及一个接收Builder的闭包用于追加存在性子查询约束。这使你可以跨多个多态类型用一条查询完成带关联条件的过滤。总结Hyperf 的模型关联系统为日常业务建模提供了完整的工具箱关联类型定义方法典型场景一对一hasOne用户 ↔ 角色一对多hasMany作者 → 多本书一对多反向belongsTo书 → 作者多对多belongsToMany用户 ↔ 角色借助中间表多态一对一morphOnemorphTo图片可属于用户或书籍多态一对多morphManymorphTo图片列表可属于用户或书籍多态多对多morphToMany带类型区分的多对多对应 MorphToMany.php 与 MorphPivot.php关键要点回顾关联以方法形式定义本质是查询构建器可无限叠加约束后取值通过动态属性访问关联__get→getAttribute→getRelationValue的调用链Model.php#L144-L147会自动完成懒加载且关联方法必须return一个Relation实例否则抛出LogicException默认外键/中间表命名均遵循约定但每个参数都支持显式覆盖多对多中间表可通过pivot或as自定义名访问用withPivot、withTimestamps、wherePivot、wherePivotIn增强使用with预加载可把 N 1 查询压缩到常量级别多态场景可用morphWith做嵌套预加载、用whereHasMorph做存在性查询通过Relation::morphMapRelation.php#L329-L339 启动期 Listener可将数据库中的多态类型别名与模型类解耦。关联的底层实现分布在 src/database/src/Model/Concerns/HasRelationships.php定义入口与 src/database/src/Model/Relations各类关联对象中动手调试或深挖细节时可以直接阅读这些文件。赞分享后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载相关推荐Hyperf 模型关联Model Relationships完整实战指南从一对一、多对多到多态关联与预加载Hyperf 模型关联Model Relationships完整实战指南从一对一、多对多到多态关联与预加载 导读 本文是 Hyperf 框架模型层关联功能后端Web框架微服务RPC框架异步编程Bookshelf.js 一对一关联One-to-One完整指南hasOne / belongsTo / morphOne 从模型定义到迁移实战Bookshelf.js 一对一关联One to One完整指南hasOne / belongsTo / morphOne 从模型定义到迁移实战 导读 本后端Reflex 数据库关系Relationships实战指南外键关联、双向关系与查询加载策略Reflex 数据库关系Relationships实战指南外键关联、双向关系与查询加载策略 在 Reflex 中模型之间通过外键Foreign Key后端前端Web框架上一篇Fresh语言开发常见问题解答新手到专家的进阶之路下一篇WhiteNoise 使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表