staudenmeir / eloquent-has-many-deep
Laravel Eloquent HasManyThrough 关系,支持无限层级
Requires
- php: ^8.2
- illuminate/database: ^11.0
- staudenmeir/eloquent-has-many-deep-contracts: ^1.2
Requires (Dev)
- awobaz/compoships: ^2.3
- barryvdh/laravel-ide-helper: ^3.0
- illuminate/pagination: ^11.0
- korridor/laravel-has-many-merged: ^1.1
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^11.0
- staudenmeir/eloquent-json-relations: ^1.11
- staudenmeir/laravel-adjacency-list: ^1.21
- dev-main
- v1.20.2
- v1.20.1
- v1.20
- 1.19.x-dev
- v1.19.4
- v1.19.3
- v1.19.2
- v1.19.1
- v1.19
- v1.18.3
- v1.18.2
- v1.18.1
- v1.18
- 1.17.x-dev
- v1.17.3
- v1.17.2
- v1.17.1
- v1.17
- v1.16
- v1.15.3
- v1.15.2
- v1.15.1
- v1.15
- 1.14.x-dev
- v1.14.4
- v1.14.3
- v1.14.2
- v1.14.1
- v1.14
- v1.13.1
- v1.13
- 1.12.x-dev
- v1.12
- 1.11.x-dev
- v1.11.1
- v1.11
- v1.10
- v1.9
- 1.8.x-dev
- v1.8
- 1.7.x-dev
- v1.7
- v1.6
- v1.5
- v1.4
- v1.3
- v1.2
- v1.1
- v1.0
- dev-phpstan-l6
- dev-phpstan-test
- dev-descendants
This package is auto-updated.
Last update: 2024-09-12 09:23:18 UTC
README
这个 HasManyThrough
扩展版本允许与无限中间模型建立关系。
它支持 多对多 和 多态 关系及其所有可能的组合。它还支持一些 第三方包。
支持 Laravel 5.5+。
安装
composer require staudenmeir/eloquent-has-many-deep:"^1.7"
如果您在 Windows 的 PowerShell 中(例如在 VS Code 中),请使用此命令
composer require staudenmeir/eloquent-has-many-deep:"^^^^1.7"
版本
用法
该包提供两种定义深度关系的方式
您可以连接 现有关系 或手动指定中间模型、外键和本地键 手动。
连接现有关系
考虑 Laravel 文档中的这个 示例,增加一个层级
Country
→ has many → User
→ has many → Post
→ has many → Comment
您可以通过连接现有关系来定义 HasManyDeep
关系
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeepFromRelations($this->posts(), (new Post())->comments()); } public function posts() { return $this->hasManyThrough(Post::class, User::class); } } class Post extends Model { public function comments() { return $this->hasMany(Comment::class); } }
如果您只想检索单个相关实例,请使用 hasOneDeepFromRelations()
定义 HasOneDeep
关系
约束
默认情况下,连接关系的约束不会转移到新的深度关系上。使用 hasManyDeepFromRelationsWithConstraints()
并将关系作为可调用的数组应用这些约束
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeepFromRelationsWithConstraints([$this, 'posts'], [new Post(), 'comments']); } public function posts() { return $this->hasManyThrough(Post::class, User::class)->where('posts.published', true); } } class Post extends Model { public function comments() { return $this->hasMany(Comment::class)->withTrashed(); } }
如果约束的列名在多个表中出现,请确保限定这些列名
->where('posts.published', true)
而不是 ->where('published', true)
第三方包
除了本机 Laravel 关系外,您还可以连接来自这些第三方包的关系
- https://github.com/korridor/laravel-has-many-merged:
HasManyMerged
- https://github.com/staudenmeir/eloquent-json-relations:
BelongsToJson
,HasManyJson
,HasManyThroughJson
- https://github.com/staudenmeir/laravel-adjacency-list: 树 & 图 关系
- https://github.com/topclaudy/compoships:
BelongsTo
,HasMany
,HasOne
手动定义关系
如果您没有所有必要的现有关系来连接它们,您也可以通过指定中间模型、外键和本地键来手动定义深层关系。
HasMany
考虑 Laravel 文档中的这个 示例,增加一个层级
Country
→ has many → User
→ has many → Post
→ has many → Comment
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]); } }
就像 hasManyThrough()
一样,hasManyDeep()
的第一个参数是相关模型。第二个参数是一个中间模型数组,从远端父模型(定义关系的模型)到相关模型。
默认情况下,hasManyDeep()
使用 Eloquent 的外键和本地键约定。您也可以将自定义外键作为第三个参数,将自定义本地键作为第四个参数指定
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( Comment::class, [User::class, Post::class], // Intermediate models, beginning at the far parent (Country). [ 'country_id', // Foreign key on the "users" table. 'user_id', // Foreign key on the "posts" table. 'post_id' // Foreign key on the "comments" table. ], [ 'id', // Local key on the "countries" table. 'id', // Local key on the "users" table. 'id' // Local key on the "posts" table. ] ); } }
您可以使用 null
代替默认键
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class], [null, 'custom_user_id']); } }
ManyToMany
您可以在中间路径中包含 ManyToMany
关系。
ManyToMany → HasMany
考虑来自 Laravel 文档的此 示例,并增加一个 HasMany
级别
User
→ 多对多 → Role
→ 有许多 → Permission
将连接表添加到中间模型
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Permission::class, ['role_user', Role::class]); } }
如果您指定了自定义键,请记住在连接表的“右侧”交换外键和本地键
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( Permission::class, ['role_user', Role::class], // Intermediate models and tables, beginning at the far parent (User). [ 'user_id', // Foreign key on the "role_user" table. 'id', // Foreign key on the "roles" table (local key). 'role_id' // Foreign key on the "permissions" table. ], [ 'id', // Local key on the "users" table. 'role_id', // Local key on the "role_user" table (foreign key). 'id' // Local key on the "roles" table. ] ); } }
ManyToMany → ManyToMany
考虑来自 Laravel 文档的此 示例,并增加一个 ManyToMany
级别
User
→ 多对多 → Role
→ 多对多 → Permission
将连接表添加到中间模型
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Permission::class, ['role_user', Role::class, 'permission_role']); } }
MorphMany
您可以在中间路径中包含 MorphMany
关系。
考虑来自 Laravel 文档的此 示例,并增加一个级别
User
→ 有许多 → Post
→ 形状许多 → Comment
将多态外键指定为数组,从 *_type
列开始
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function postComments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( Comment::class, [Post::class], [null, ['commentable_type', 'commentable_id']] ); } }
MorphToMany
您可以在中间路径中包含 MorphToMany
关系。
考虑来自 Laravel 文档的此 示例,并增加一个级别
User
→ 有许多 → Post
→ 形状到多 → Tag
将连接表添加到中间模型,并将多态外键指定为数组,从 *_type
列开始
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function postTags(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( Tag::class, [Post::class, 'taggables'], [null, ['taggable_type', 'taggable_id'], 'id'], [null, null, 'tag_id'] ); } }
请记住在连接表的“右侧”交换外键和本地键
MorphedByMany
您可以在中间路径中包含 MorphedByMany
关系。
考虑来自 Laravel 文档的此 示例,并增加一个级别
Tag
→ 形状由多 → Post
→ 有许多 → Comment
将连接表添加到中间模型,并将多态本地键指定为数组,从 *_type
列开始
class Tag extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function postComments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( Comment::class, ['taggables', Post::class], [null, 'id'], [null, ['taggable_type', 'taggable_id']] ); } }
BelongsTo
您可以在中间路径中包含 BelongsTo
关系。
Tag
→ 形状由多 → Post
→ 属于 → User
交换外键和本地键
class Tag extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function postAuthors(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( User::class, ['taggables', Post::class], [null, 'id', 'id'], [null, ['taggable_type', 'taggable_id'], 'user_id'] ); } }
HasOneDeep
如果您只想检索单个相关实例,请定义 HasOneDeep
关系
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function latestComment(): \Staudenmeir\EloquentHasManyDeep\HasOneDeep { return $this->hasOneDeep(Comment::class, [User::class, Post::class]) ->latest('comments.created_at'); } }
复合键
如果两个表之间需要匹配多个列,您可以使用 CompositeKey
类定义复合键。
考虑来自 compoships
文档的此 示例,并增加一个级别
User
→ 有许多(匹配 team_id
& category_id
)→ Task
→ 属于 → Project
use Staudenmeir\EloquentHasManyDeep\Eloquent\CompositeKey; class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function projects(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep( Project::class, [Task::class], [new CompositeKey('team_id', 'category_id'), 'id'], [new CompositeKey('team_id', 'category_id'), 'project_id'] ); } }
中间和枢纽数据
使用 withIntermediate()
检索中间表的属性
public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]) ->withIntermediate(Post::class); } foreach ($country->comments as $comment) { // $comment->post->title }
默认情况下,这将检索表的所有列。请注意,这将执行一个单独的查询来获取列的列表。
您可以将选定的列作为第二个参数指定
public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]) ->withIntermediate(Post::class, ['id', 'title']); }
作为第三个参数,您可以选择自定义访问器
public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]) ->withIntermediate(Post::class, ['id', 'title'], 'accessor'); } foreach ($country->comments as $comment) { // $comment->accessor->title }
如果您从多个表中检索数据,可以使用嵌套访问器
public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]) ->withIntermediate(Post::class) ->withIntermediate(User::class, ['*'], 'post.user'); } foreach ($country->comments as $comment) { // $comment->post->title // $comment->post->user->name }
使用 withPivot()
为 BelongsToMany
和 MorphToMany
/MorphedByMany
关系中的数据透视表
public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Permission::class, ['role_user', Role::class]) ->withPivot('role_user', ['expires_at']); } foreach ($user->permissions as $permission) { // $permission->role_user->expires_at }
您可以将自定义数据透视模型作为第三个参数,并将自定义访问器作为第四个参数
public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Permission::class, ['role_user', Role::class]) ->withPivot('role_user', ['expires_at'], RoleUser::class, 'pivot'); } foreach ($user->permissions as $permission) { // $permission->pivot->expires_at }
中间和枢纽约束
您可以对中间表和数据透视表应用约束
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]); } } $commentsFromActiveUsers = $country->comments()->where('users.active', true)->get();
表别名
如果您的关联路径中包含相同的模型多次,您可以指定一个表别名
class Post extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function commentReplies(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [Comment::class . ' as alias'], [null, 'parent_id']); } }
在您要别名的模型中使用 HasTableAlias
特性
class Comment extends Model { use \Staudenmeir\EloquentHasManyDeep\HasTableAlias; }
对于数据透视表,这需要自定义模型
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Permission::class, [RoleUser::class . ' as alias', Role::class]); } } class RoleUser extends Pivot { use \Staudenmeir\EloquentHasManyDeep\HasTableAlias; }
使用 setAlias()
在连接现有关系时指定表别名
class Post extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function commentReplies(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeepFromRelations( $this->comments(), (new Comment())->setAlias('alias')->replies() ); } public function comments() { return $this->hasMany(Comment::class); } } class Comment extends Model { use \Staudenmeir\EloquentHasManyDeep\HasTableAlias; public function replies() { return $this->hasMany(self::class, 'parent_id'); } }
软删除
默认情况下,软删除的中间模型将被排除在结果之外。使用 withTrashed()
包含它们
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]) ->withTrashed('users.deleted_at'); } } class User extends Model { use SoftDeletes; }
获取唯一结果
与多对多段的多级关系可能在其结果中包含重复的模型。如果您想要获取唯一的结果,可以从结果集合中删除重复项
$uniqueComments = Country::find($id)->comments()->get()->unique();
如果您需要在查询中删除重复项(例如,用于分页),请尝试添加 distinct()
$uniqueComments = Country::find($id)->comments()->distinct()->get();
distinct()
并非对所有情况都有效。如果它对您不起作用,请使用 groupBy()
代替
$uniqueComments = Country::find($id)->comments() ->getQuery() // Get the underlying query builder ->select('comments.*') // Select only columns from the related table ->groupBy('comments.id') // Group by the related table's primary key ->get();
反转关系
您可以通过使用 hasManyDeepFromReverse()
/hasOneDeepFromReverse()
反转现有的多级关系来定义 HasManyDeep
/HasOneDeep
关系
class Country extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function comments(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeep(Comment::class, [User::class, Post::class]); } } class Comment extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function country(): \Staudenmeir\EloquentHasManyDeep\HasOneDeep { return $this->hasOneDeepFromReverse( (new Country())->comments() ); } }
IDE 辅助工具
如果您使用 barryvdh/laravel-ide-helper,此包提供了一个模型钩子,当生成类型提示时会正确添加关系。默认情况下,使用 Package Discovery 启用模型钩子。
要手动启用它,请将 model hook 添加到 model_hooks 数组中。
// File: config/ide-helper.php /* |-------------------------------------------------------------------------- | Models hooks |-------------------------------------------------------------------------- | | Define which hook classes you want to run for models to add custom information | | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. | */ 'model_hooks' => [ \Staudenmeir\EloquentHasManyDeep\IdeHelper\DeepRelationsHook::class, ],
要禁用模型钩子,您有三个选择
使用 .env 禁用
更新您的 .env
文件以包含
ELOQUENT_HAS_MANY_DEEP_IDE_HELPER_ENABLED=false
使用配置禁用
发布配置并直接禁用设置
php artisan vendor:publish --tag=eloquent-has-many-deep
// File: config/eloquent-has-many-deep.php /* |-------------------------------------------------------------------------- | IDE Helper |-------------------------------------------------------------------------- | | Automatically register the model hook to receive correct type hints | */ 'ide_helper_enabled' => false,
通过退出 Package Discovery 禁用
使用以下内容更新您的 composer.json
"extra": { "laravel": { "dont-discover": [ "staudenmeir/eloquent-has-many-deep" ] } },
贡献
有关详细信息,请参阅 CONTRIBUTING 和 CODE OF CONDUCT