From 0cc3a992bcbe081ae21301566523b5851385f0d1 Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Sat, 19 Nov 2022 10:53:26 +0100 Subject: [PATCH] Document relationship concatenation and HasManyThroughJson relationships --- README.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8f0acd7..f58f432 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ [![License](https://poser.pugx.org/staudenmeir/eloquent-json-relations/license)](https://packagist.org/packages/staudenmeir/eloquent-json-relations) ## Introduction -This Laravel Eloquent extension adds support for JSON foreign keys to `BelongsTo`, `HasOne`, `HasMany`, `HasOneThrough`, `HasManyThrough`, `MorphTo`, `MorphOne` and `MorphMany` relationships. + +This Laravel Eloquent extension adds support for JSON foreign keys to `BelongsTo`, `HasOne`, `HasMany`, `HasOneThrough` +, `HasManyThrough`, `MorphTo`, `MorphOne` and `MorphMany` relationships. It also provides [many-to-many](#many-to-many-relationships) relationships with JSON arrays. ## Compatibility @@ -30,15 +32,18 @@ Use this command if you are in PowerShell on Windows (e.g. in VS Code): ## Usage - [One-To-Many Relationships](#one-to-many-relationships) - - [Referential Integrity](#referential-integrity) + - [Referential Integrity](#referential-integrity) - [Many-To-Many Relationships](#many-to-many-relationships) - - [Array of IDs](#array-of-ids) - - [Array of Objects](#array-of-objects) - - [Query Performance](#query-performance) + - [Array of IDs](#array-of-ids) + - [Array of Objects](#array-of-objects) + - [Query Performance](#query-performance) +- [Has-Many-Through Relationships](#has-many-through-relationships) +- [Concatenation](#concatenation) ### One-To-Many Relationships -In this example, `User` has a `BelongsTo` relationship with `Locale`. There is no dedicated column, but the foreign key (`locale_id`) is stored as a property in a JSON field (`users.options`): +In this example, `User` has a `BelongsTo` relationship with `Locale`. There is no dedicated column, but the foreign +key (`locale_id`) is stored as a property in a JSON field (`users.options`): ```php class User extends Model @@ -70,7 +75,10 @@ Remember to use the `HasJsonRelationships` trait in both the parent and the rela #### Referential Integrity -On [MySQL](https://dev.mysql.com/doc/refman/en/create-table-foreign-keys.html), [MariaDB](https://mariadb.com/kb/en/library/foreign-keys/) and [SQL Server](https://docs.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table) you can still ensure referential integrity with foreign keys on generated/computed columns. +On [MySQL](https://dev.mysql.com/doc/refman/en/create-table-foreign-keys.html) +, [MariaDB](https://mariadb.com/kb/en/library/foreign-keys/) +and [SQL Server](https://docs.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table) +you can still ensure referential integrity with foreign keys on generated/computed columns. Laravel migrations support this feature on MySQL/MariaDB: @@ -84,7 +92,7 @@ Schema::create('users', function (Blueprint $table) { }); ``` -Laravel migrations (5.7.25+) also support this feature on SQL Server: +Laravel migrations (5.7.25+) also support this feature on SQL Server: ```php Schema::create('users', function (Blueprint $table) { @@ -97,7 +105,8 @@ Schema::create('users', function (Blueprint $table) { }); ``` -There is a [workaround](https://github.com/staudenmeir/eloquent-json-relations/tree/1.1#referential-integrity) for older versions of Laravel. +There is a [workaround](https://github.com/staudenmeir/eloquent-json-relations/tree/1.1#referential-integrity) for older +versions of Laravel. ### Many-To-Many Relationships @@ -105,7 +114,8 @@ The package also introduces two new relationship types: `BelongsToJson` and `Has On Laravel 5.6.25+, you can use them to implement many-to-many relationships with JSON arrays. -In this example, `User` has a `BelongsToMany` relationship with `Role`. There is no pivot table, but the foreign keys are stored as an array in a JSON field (`users.options`): +In this example, `User` has a `BelongsToMany` relationship with `Role`. There is no pivot table, but the foreign keys +are stored as an array in a JSON field (`users.options`): #### Array of IDs @@ -180,7 +190,8 @@ class Role extends Model } ``` -Here, `options->roles` is the path to the JSON array. `role_id` is the name of the foreign key property inside the record object: +Here, `options->roles` is the path to the JSON array. `role_id` is the name of the foreign key property inside the +record object: ```php $user = new User; @@ -201,7 +212,8 @@ $user->roles()->toggle([2 => ['active' => true], 3])->save(); #### Query Performance -On PostgreSQL, you can improve the query performance with `jsonb` columns and [`GIN` indexes](https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING). +On PostgreSQL, you can improve the query performance with `jsonb` columns +and [`GIN` indexes](https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING). Use this migration when the array of IDs/objects is the column itself (e.g. `users.role_ids`): @@ -224,6 +236,94 @@ Schema::create('users', function (Blueprint $table) { }); ``` +### Has-Many-Through Relationships + +Similar to Laravel's [`HasManyThrough`](https://laravel.com/docs/9.x/eloquent-relationships#has-many-through), you can +define `HasManyThroughJson` relationship when the JSON column is in the intermediate table (Laravel 9+). This +requires [staudenmeir/eloquent-has-many-deep](https://github.com/staudenmeir/eloquent-has-many-deep). + +Consider a relationship between `Role` and `Project` through `User`: + +`Role` → has many JSON → `User` → has many `Project` + +[Install](https://github.com/staudenmeir/eloquent-has-many-deep/#installation) the additional package, add the +`HasRelationships` trait to the parent (first) model and pass the JSON column as a `JsonKey` object: + +```php +class Role extends Model +{ + use \Staudenmeir\EloquentHasManyDeep\HasRelationships; + + public function projects() + { + return $this->hasManyThroughJson( + Project::class, User::class, new JsonKey('options->role_ids') + ); + } +} +``` + +The reverse relationship would look like this: + +```php +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') + ); + } +} +``` + +### Concatenation + +You can include JSON relationships into deep relationships by concatenating them with other relationships +using [staudenmeir/eloquent-has-many-deep](https://github.com/staudenmeir/eloquent-has-many-deep) (Laravel 9+). + +Consider a relationship between `User` and `Permission` through `Role`: + +`User` → belongs to JSON → `Role` → has many → `Permission` + +[Install](https://github.com/staudenmeir/eloquent-has-many-deep/#installation) the additional package, add the +`HasRelationships` trait to the parent (first) model +and [define](https://github.com/staudenmeir/eloquent-has-many-deep/#concatenating-existing-relationships) a +deep relationship: + +```php +class User extends Model +{ + use \Staudenmeir\EloquentHasManyDeep\HasRelationships; + use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships; + + public function permissions() + { + return $this->hasManyDeepFromRelations( + $this->roles(), + (new Role)->permissions() + ); + } + + public function roles() + { + 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 Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CODE OF CONDUCT](.github/CODE_OF_CONDUCT.md) for details.