paxha / has-many-through-deep
Laravel Eloquent 的无限层级 HasManyThrough 关系
Requires
- php: ^7.2
- illuminate/database: ^6.0
Requires (Dev)
- illuminate/pagination: ^6.0
- laravel/homestead: ^9.0
- paxha/eloquent-eager-limit: ^1.4
- phpunit/phpunit: ^8.0
README
简介
此 HasManyThrough
扩展版本允许与无限中间模型建立关系。
它支持 多对多 和 多态 关系及其所有可能的组合。
支持 Laravel 5.5.29+。
安装
composer require paxha/has-many-through-deep
版本
用法
该包提供了两种定义深度关系的方法
您可以通过手动指定中间模型、外键和本地键,或者连接 现有关系 来定义它们。
HasMany
考虑带有额外层级的 文档示例
Country
→ has many → User
→ has many → Post
→ has many → Comment
class Country extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post']); } }
hasManyDeep()
的第一个参数是相关模型。第二个参数是一个数组,包含中间模型,从父模型(定义关系的模型)到相关模型。
默认情况下,hasManyDeep()
使用 Eloquent 的外键和本地键约定。您还可以指定自定义外键作为第三个参数,自定义本地键作为第四个参数
class Country extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function comments() { return $this->hasManyDeep( 'App\Comment', ['App\User', 'App\Post'], // 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 \Paxha\EloquentHasManyDeep\HasRelationships; public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post'], [null, 'custom_user_id']); } }
BelongsToMany
您可以将 BelongsToMany
关系包含在中间路径中。
考虑带有额外层级的 文档示例
User
→ belongs to many → Role
→ has many → Permission
将连接表添加到中间模型
class User extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function permissions() { return $this->hasManyDeep('App\Permission', ['role_user', 'App\Role']); } }
如果您指定了自定义键,请记住在连接表的“右侧”交换外键和本地键
class User extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function permissions() { return $this->hasManyDeep( 'App\Permission', ['role_user', 'App\Role'], // 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. ] ); } }
MorphMany
您可以将 MorphMany
关系包含在中间路径中。
考虑带有额外层级的 文档示例
User
→ has many → Post
→ morph many → Comment
将多态外键指定为数组,从 *_type
列开始
class User extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function postComments() { return $this->hasManyDeep( 'App\Comment', ['App\Post'], [null, ['commentable_type', 'commentable_id']] ); } }
MorphToMany
您可以将 MorphToMany
关系包含在中间路径中。
考虑带有额外层级的 文档示例
User
→ has many → Post
→ morph to many → Tag
将连接表添加到中间模型,并将多态外键指定为数组,从 *_type
列开始
class User extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function postTags() { return $this->hasManyDeep( 'App\Tag', ['App\Post', 'taggables'], [null, ['taggable_type', 'taggable_id'], 'id'], [null, null, 'tag_id'] ); } }
请记住在连接表的“右侧”交换外键和本地键
MorphedByMany
您可以将 MorphedByMany
关系包含在中间路径中。
考虑带有额外层级的 文档示例
Tag
→ morphed by many → Post
→ has many → Comment
将连接表添加到中间模型,并将多态本地键指定为数组,从 *_type
列开始
class Tag extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function postComments() { return $this->hasManyDeep( 'App\Comment', ['taggables', 'App\Post'], [null, 'id'], [null, ['taggable_type', 'taggable_id']] ); } }
BelongsTo
您可以在中间路径中包含 BelongsTo
关系
Tag
→ 多对一转换 → Post
→ 属于 → User
交换外键和本地键
class Tag extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function postAuthors() { return $this->hasManyDeep( 'App\User', ['taggables', 'App\Post'], [null, 'id', 'id'], [null, ['taggable_type', 'taggable_id'], 'user_id'] ); } }
现有关系
您也可以通过连接现有关系来定义 HasManyDeep
关系
class Country extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function comments() { return $this->hasManyDeepFromRelations($this->posts(), (new Post)->comments()); } public function posts() { return $this->hasManyThrough('App\Post', 'App\User'); } } class Post extends Model { public function comments() { return $this->hasMany('App\Comment'); } }
HasOneDeep
如果您只想检索单个相关实例,请定义 HasOneDeep
关系
class Country extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function latestComment() { return $this->hasOneDeep('App\Comment', ['App\User', 'App\Post']) ->latest('comments.created_at'); } }
中间和关联数据
使用 withIntermediate()
来检索中间表的属性
public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post']) ->withIntermediate('App\Post'); } foreach ($country->comments as $comment) { // $comment->post->title }
默认情况下,这将检索表的所有列。请注意,这会执行一个单独的查询来获取列的列表。
您可以将选定的列作为第二个参数指定
public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post']) ->withIntermediate('App\Post', ['id', 'title']); }
作为第三个参数,您可以指定自定义访问器
public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post']) ->withIntermediate('App\Post', ['id', 'title'], 'accessor'); } foreach ($country->comments as $comment) { // $comment->accessor->title }
如果您从多个表中检索数据,您可以使用嵌套访问器
public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post']) ->withIntermediate('App\Post') ->withIntermediate('App\User', ['*'], 'post.user'); } foreach ($country->comments as $comment) { // $comment->post->title // $comment->post->user->name }
使用 withPivot()
来处理 BelongsToMany
和 MorphToMany
/MorphedByMany
关系的交叉表
public function permissions() { return $this->hasManyDeep('App\Permission', ['role_user', 'App\Role']) ->withPivot('role_user', ['expires_at']); } foreach ($user->permissions as $permission) { // $permission->role_user->expires_at }
您可以将自定义交叉模型作为第三个参数,并将自定义访问器作为第四个参数指定
public function permissions() { return $this->hasManyDeep('App\Permission', ['role_user', 'App\Role']) ->withPivot('role_user', ['expires_at'], 'App\RoleUser', 'pivot'); } foreach ($user->permissions as $permission) { // $permission->pivot->expires_at }
表别名
如果您的关联路径包含相同的模型多次,您可以指定一个表别名
class Post extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function commentReplies() { return $this->hasManyDeep('App\Comment', ['App\Comment as alias'], [null, 'parent_id']); } }
在您别名的模型中使用 HasTableAlias
特性
class Comment extends Model { use \Paxha\EloquentHasManyDeep\HasTableAlias; }
对于交叉表,这需要自定义模型
class User extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function permissions() { return $this->hasManyDeep('App\Permission', ['App\RoleUser as alias', 'App\Role']); } } class RoleUser extends Pivot { use \Paxha\EloquentHasManyDeep\HasTableAlias; }
使用 setAlias()
在连接现有关系时指定表别名
class Post extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function commentReplies() { return $this->hasManyDeepFromRelations( $this->comments(), (new Comment)->setAlias('alias')->replies() ); } public function comments() { return $this->hasMany(Comment::class); } } class Comment extends Model { use \Paxha\EloquentHasManyDeep\HasTableAlias; public function replies() { return $this->hasMany(self::class, 'parent_id'); } }
软删除
默认情况下,软删除的中间模型将被排除在结果之外。使用 withTrashed()
来包含它们
class Country extends Model { use \Paxha\EloquentHasManyDeep\HasRelationships; public function comments() { return $this->hasManyDeep('App\Comment', ['App\User', 'App\Post']) ->withTrashed('users.deleted_at'); } } class User extends Model { use SoftDeletes; }