staudenmeir / eloquent-json-relations
Laravel Eloquent JSON键关系的扩展
Requires
- php: ^8.2
- illuminate/database: ^11.0
- staudenmeir/eloquent-has-many-deep-contracts: ^1.2
Requires (Dev)
- barryvdh/laravel-ide-helper: ^3.0
- orchestra/testbench: ^9.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^11.0
- staudenmeir/eloquent-has-many-deep: ^1.20
- dev-main
- v1.13
- v1.12
- v1.11
- 1.10.x-dev
- v1.10.2
- v1.10.1
- v1.10
- v1.9.1
- v1.9
- v1.8.3
- v1.8.2
- v1.8.1
- v1.8
- 1.7.x-dev
- v1.7.3
- v1.7.2
- v1.7.1
- v1.7
- v1.6.3
- v1.6.2
- v1.6.1
- v1.6
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5
- 1.4.x-dev
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4
- 1.3.x-dev
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3
- 1.2.x-dev
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2
- 1.1.x-dev
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1
- v1.0
- dev-sqlsrv
- dev-TODO
- dev-nested-arrays-test
This package is auto-updated.
Last update: 2024-09-11 18:23:28 UTC
README
这个Laravel Eloquent扩展增加了对BelongsTo
、HasOne
、HasMany
、HasOneThrough
、HasManyThrough
、MorphTo
、MorphOne
和MorphMany
关系的JSON外键支持。
兼容性
- MySQL 5.7+
- MariaDB 10.2+
- PostgreSQL 9.3+
- SQLite 3.38+
- SQL Server 2016+
安装
composer require "staudenmeir/eloquent-json-relations:^1.1"
如果你在Windows的PowerShell中(例如在VS Code中),请使用此命令
composer require "staudenmeir/eloquent-json-relations:^^^^1.1"
版本
用法
一对一关系
在这个例子中,User
与Locale
有BelongsTo
关系。没有专门的列,但外键(locale_id
)作为属性存储在JSON字段(users.options
)中。
class User extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; protected $casts = [ 'options' => 'json', ]; public function locale() { return $this->belongsTo(Locale::class, 'options->locale_id'); } } class Locale extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; public function users() { return $this->hasMany(User::class, 'options->locale_id'); } }
请记住在父模型和相关模型中都使用HasJsonRelationships
特性。
引用完整性
在MySQL、MariaDB和SQL Server上,您仍然可以通过生成/计算列上的外键来确保引用完整性。
Laravel迁移在MySQL/MariaDB上支持此功能
Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->json('options'); $locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id'); $table->unsignedBigInteger('locale_id')->storedAs($locale_id); $table->foreign('locale_id')->references('id')->on('locales'); });
Laravel迁移也支持SQL Server上的此功能
Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->json('options'); $locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id'); $locale_id = 'CAST('.$locale_id.' AS INT)'; $table->computed('locale_id', $locale_id)->persisted(); $table->foreign('locale_id')->references('id')->on('locales'); });
多对多关系
该包还引入了两种新的关系类型:BelongsToJson
和HasManyJson
使用它们实现与JSON数组的多对多关系。
在这个例子中,User
与Role
有BelongsToMany
关系。没有中间表,但外键作为数组存储在JSON字段(users.options
)中。
ID数组
默认情况下,关系将中继记录存储为ID数组
class User extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; protected $casts = [ 'options' => 'json', ]; public function roles(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson { return $this->belongsToJson(Role::class, 'options->role_ids'); } } class Role extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; public function users(): \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson { return $this->hasManyJson(User::class, 'options->role_ids'); } }
在BelongsToJson
关系的一侧,您可以使用attach()
、detach()
、sync()
和toggle()
$user = new User; $user->roles()->attach([1, 2])->save(); // Now: [1, 2] $user->roles()->detach([2])->save(); // Now: [1] $user->roles()->sync([1, 3])->save(); // Now: [1, 3] $user->roles()->toggle([2, 3])->save(); // Now: [1, 2]
对象数组
您也可以将中继记录作为具有附加属性的对象存储
class User extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; protected $casts = [ 'options' => 'json', ]; public function roles(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson { return $this->belongsToJson(Role::class, 'options->roles[]->role_id'); } } class Role extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; public function users(): \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson { return $this->hasManyJson(User::class, 'options->roles[]->role_id'); } }
在这里,options->roles
是JSON数组的路径。role_id
是记录对象中外键属性的名称
$user = new User; $user->roles()->attach([1 => ['active' => true], 2 => ['active' => false]])->save(); // Now: [{"role_id":1,"active":true},{"role_id":2,"active":false}] $user->roles()->detach([2])->save(); // Now: [{"role_id":1,"active":true}] $user->roles()->sync([1 => ['active' => false], 3 => ['active' => true]])->save(); // Now: [{"role_id":1,"active":false},{"role_id":3,"active":true}] $user->roles()->toggle([2 => ['active' => true], 3])->save(); // Now: [{"role_id":1,"active":false},{"role_id":2,"active":true}]
限制:在SQLite和SQL Server上,这些关系只能部分工作。
HasOneJson
如果您只想检索单个相关实例,请定义HasOneJson
关系
class Role extends Model { use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; public function latestUser(): \Staudenmeir\EloquentJsonRelations\Relations\HasOneJson { return $this->hasOneJson(User::class, 'options->roles[]->role_id') ->latest(); } }
复合键
如果需要多个列匹配,您可以定义复合键
传递以JSON键开始的键的数组
class Employee extends Model { public function tasks(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson { return $this->belongsToJson( Task::class, ['options->work_stream_ids', 'team_id'], ['work_stream_id', 'team_id'] ); } } class Task extends Model { public function employees(): \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson { return $this->hasManyJson( Employee::class, ['options->work_stream_ids', 'team_id'], ['work_stream_id', 'team_id'] ); } }
查询性能
MySQL
在MySQL 8.0.17+上,您可以通过多值索引来提高查询性能。
在数组是列本身时(例如,users.role_ids
)使用此迁移
Schema::create('users', function (Blueprint $table) { // ... // Array of IDs $table->rawIndex('(cast(`role_ids` as unsigned array))', 'users_role_ids_index'); // Array of objects $table->rawIndex('(cast(`roles`->\'$[*]."role_id"\' as unsigned array))', 'users_roles_index'); });
在数组嵌套在对象内部时(例如,users.options->role_ids
)使用此迁移
Schema::create('users', function (Blueprint $table) { // ... // Array of IDs $table->rawIndex('(cast(`options`->\'$."role_ids"\' as unsigned array))', 'users_role_ids_index'); // Array of objects $table->rawIndex('(cast(`options`->\'$."roles"[*]."role_id"\' as unsigned array))', 'users_roles_index'); });
MySQL 对语法非常挑剔,因此我建议您使用 EXPLAIN
检查一下执行的关系查询实际上是否使用了索引。
PostgreSQL
在 PostgreSQL 中,您可以使用 jsonb
列和 GIN
索引来提高查询性能。
当 ID/对象的数组是列本身时(例如 users.role_ids
),使用此迁移。
Schema::create('users', function (Blueprint $table) { $table->id(); $table->jsonb('role_ids'); $table->index('role_ids')->algorithm('gin'); });
在数组嵌套在对象内部时(例如,users.options->role_ids
)使用此迁移
Schema::create('users', function (Blueprint $table) { $table->id(); $table->jsonb('options'); $table->rawIndex('("options"->\'role_ids\')', 'users_options_index')->algorithm('gin'); });
Has-Many-Through 关系
类似于 Laravel 的 HasManyThrough
,当 JSON 列在中间表(Laravel 9+)中时,您可以定义 HasManyThroughJson
关系。这需要 staudenmeir/eloquent-has-many-deep。
考虑 Role
和 Project
通过 User
之间的关系
Role
→ 有多个 JSON → User
→ 有多个 Project
安装附加包,将 HasRelationships
特性添加到父(第一个)模型,并将 JSON 列作为 JsonKey
对象传递
class Role extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function projects() { return $this->hasManyThroughJson( Project::class, User::class, new \Staudenmeir\EloquentJsonRelations\JsonKey('options->role_ids') ); } }
反向关系看起来像这样
class Project extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; public function roles() { return $this->hasManyThroughJson( Role::class, User::class, 'id', 'id', 'user_id', new JsonKey('options->role_ids') ); } }
深度关系连接
您可以使用 staudenmeir/eloquent-has-many-deep(Laravel 9+)将 JSON 关系包括到深层关系中,通过将它们与其他关系连接起来。
考虑 User
和 Permission
通过 Role
之间的关系
User
→ 属于 JSON → Role
→ 有多个 → Permission
安装附加包,将 HasRelationships
特性添加到父(第一个)模型,并 定义一个深层关系
class User extends Model { use \Staudenmeir\EloquentHasManyDeep\HasRelationships; use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep { return $this->hasManyDeepFromRelations( $this->roles(), (new Role)->permissions() ); } public function roles(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson { return $this->belongsToJson(Role::class, 'options->role_ids'); } } class Role extends Model { public function permissions() { return $this->hasMany(Permission::class); } } $permissions = User::find($id)->permissions;
贡献
请参阅 CONTRIBUTING 和 CODE OF CONDUCT 以获取详细信息。