Skip to content

Commit

Permalink
Add @hasOneThrough directive
Browse files Browse the repository at this point in the history
  • Loading branch information
saeed-rostami authored Jul 19, 2024
1 parent 921981c commit d1c8036
Show file tree
Hide file tree
Showing 42 changed files with 353 additions and 121 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

## v6.42.0

### Added

- Add `@hasOneThrough` directive https://github.com/nuwave/lighthouse/pull/2585

## v6.41.1

### Fixed
Expand Down
39 changes: 37 additions & 2 deletions docs/6/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ type User {
}
```
If the name of the relationship on the Eloquent model is different than the field name,
If the name of the relationship on the Eloquent model differs from the field name,
you can override it by setting `relation`.
```graphql
Expand Down Expand Up @@ -1841,7 +1841,7 @@ type User {
}
```
If the name of the relationship on the Eloquent model is different than the field name,
If the name of the relationship on the Eloquent model differs from the field name,
you can override it by setting `relation`.
```graphql
Expand All @@ -1850,6 +1850,41 @@ type User {
}
```
## @hasOneThrough
```graphql
"""
Corresponds to [the Eloquent relationship HasOneThrough](https://laravel.com/docs/eloquent-relationships#has-one-through).
"""
directive @hasOneThrough(
"""
Specify the relationship method name in the model class,
if it is named different from the field in the schema.
"""
relation: String
"""
Apply scopes to the underlying query.
"""
scopes: [String!]
) on FIELD_DEFINITION
```
```graphql
type Mechanic {
carOwner: Owner! @hasOneThrough
}
```
If the name of the relationship on the Eloquent model differs from the field name,
you can override it by setting `relation`.
```graphql
type Mechanic {
carOwner: Owner! @hasOneThrough(relation: "owner")
}
```
## @in
```graphql
Expand Down
10 changes: 10 additions & 0 deletions docs/6/eloquent/relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ type Role {
}
```

## Has One Through

Use the [@hasOneThrough](../api-reference/directives.md#hasonethrough) directive to define a [has-one-through relationship](https://laravel.com/docs/eloquent-relationships#has-one-through).

```graphql
type Mechanic {
carOwner: Owner! @hasOneThrough
}
```

## Has Many Through

Use the [@hasManyThrough](../api-reference/directives.md#hasmanythrough) directive to define a [has-many-through relationship](https://laravel.com/docs/eloquent-relationships#has-many-through).
Expand Down
39 changes: 37 additions & 2 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ type User {
}
```
If the name of the relationship on the Eloquent model is different than the field name,
If the name of the relationship on the Eloquent model differs from the field name,
you can override it by setting `relation`.
```graphql
Expand Down Expand Up @@ -1841,7 +1841,7 @@ type User {
}
```
If the name of the relationship on the Eloquent model is different than the field name,
If the name of the relationship on the Eloquent model differs from the field name,
you can override it by setting `relation`.
```graphql
Expand All @@ -1850,6 +1850,41 @@ type User {
}
```
## @hasOneThrough
```graphql
"""
Corresponds to [the Eloquent relationship HasOneThrough](https://laravel.com/docs/eloquent-relationships#has-one-through).
"""
directive @hasOneThrough(
"""
Specify the relationship method name in the model class,
if it is named different from the field in the schema.
"""
relation: String
"""
Apply scopes to the underlying query.
"""
scopes: [String!]
) on FIELD_DEFINITION
```
```graphql
type Mechanic {
carOwner: Owner! @hasOneThrough
}
```
If the name of the relationship on the Eloquent model differs from the field name,
you can override it by setting `relation`.
```graphql
type Mechanic {
carOwner: Owner! @hasOneThrough(relation: "owner")
}
```
## @in
```graphql
Expand Down
10 changes: 10 additions & 0 deletions docs/master/eloquent/relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ type Role {
}
```

## Has One Through

Use the [@hasOneThrough](../api-reference/directives.md#hasonethrough) directive to define a [has-one-through relationship](https://laravel.com/docs/eloquent-relationships#has-one-through).

```graphql
type Mechanic {
carOwner: Owner! @hasOneThrough
}
```

## Has Many Through

Use the [@hasManyThrough](../api-reference/directives.md#hasmanythrough) directive to define a [has-many-through relationship](https://laravel.com/docs/eloquent-relationships#has-many-through).
Expand Down
27 changes: 27 additions & 0 deletions src/Schema/Directives/HasOneThroughDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);

namespace Nuwave\Lighthouse\Schema\Directives;

class HasOneThroughDirective extends RelationDirective
{
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Corresponds to [the Eloquent relationship HasOneThrough](https://laravel.com/docs/eloquent-relationships#has-one-through).
"""
directive @hasOneThrough(
"""
Specify the relationship method name in the model class,
if it is named different from the field in the schema.
"""
relation: String
"""
Apply scopes to the underlying query.
"""
scopes: [String!]
) on FIELD_DEFINITION
GRAPHQL;
}
}
61 changes: 61 additions & 0 deletions tests/Integration/Schema/Directives/HasOneThroughDirectiveTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php declare(strict_types=1);

namespace Tests\Integration\Schema\Directives;

use Tests\DBTestCase;
use Tests\Utils\Models\Post;
use Tests\Utils\Models\PostStatus;

final class HasOneThroughDirectiveTest extends DBTestCase
{
public function testQueryHasOneThroughRelationship(): void
{
$this->schema = /** @lang GraphQL */ '
type Query {
tasks: [Task!]! @all
}
type Task {
id: ID!
postStatus: PostStatus @hasOneThrough
}
type PostStatus {
id: ID!
status: String
}
';

$post = factory(Post::class)->create();
assert($post instanceof Post);

$postStatus = factory(PostStatus::class)->make();
assert($postStatus instanceof PostStatus);
$postStatus->post()->associate($post);
$postStatus->save();

$this->graphQL(/** @lang GraphQL */ '
{
tasks {
id
postStatus {
id
status
}
}
}
')->assertExactJson([
'data' => [
'tasks' => [
[
'id' => (string) $post->task->id,
'postStatus' => [
'id' => (string) $postStatus->id,
'status' => $postStatus->status,
],
],
],
],
]);
}
}
69 changes: 40 additions & 29 deletions tests/Utils/Models/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
Expand All @@ -28,43 +29,53 @@
* @property int|null $user_id
* @property int $task_id
* @property int|null $parent_id
* @property-read \Tests\Utils\Models\User $user
*
* Relations
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Activity> $activity
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Comment> $comments
* @property-read \Tests\Utils\Models\Task $task
* @property-read \Tests\Utils\Models\Post $parent
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Category> $categories
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Post> $children
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Tag> $tags
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Comment> $comments
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Image> $images
* @property-read \Tests\Utils\Models\Post|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\RoleUserPivot> $roles
* @property-read \Tests\Utils\Models\PostStatus|null $status
* @property-read \Illuminate\Database\Eloquent\Collection<\Tests\Utils\Models\Tag> $tags
* @property-read \Tests\Utils\Models\Task $task
* @property-read \Tests\Utils\Models\User|null $user
*/
final class Post extends Model
{
use Searchable;
use SoftDeletes;

/** @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Tests\Utils\Models\User, self> */
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

/** @return \Illuminate\Database\Eloquent\Relations\MorphMany<\Tests\Utils\Models\Activity> */
public function activity(): MorphMany
{
return $this->morphMany(Activity::class, 'content');
}

/** @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Tests\Utils\Models\Category> */
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'category_post', 'category_id', 'post_id');
}

/** @return \Illuminate\Database\Eloquent\Relations\HasMany<self> */
public function children(): HasMany
{
return $this->hasMany(self::class);
}

/** @return \Illuminate\Database\Eloquent\Relations\HasMany<\Tests\Utils\Models\Comment> */
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}

/** @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Tests\Utils\Models\Task, self> */
public function task(): BelongsTo
/** @return \Illuminate\Database\Eloquent\Relations\MorphMany<\Tests\Utils\Models\Image> */
public function images(): MorphMany
{
return $this->belongsTo(Task::class);
return $this->morphMany(Image::class, 'imageable');
}

/** @return \Illuminate\Database\Eloquent\Relations\BelongsTo<self, self> */
Expand All @@ -73,33 +84,33 @@ public function parent(): BelongsTo
return $this->belongsTo(self::class);
}

/** @return \Illuminate\Database\Eloquent\Relations\HasMany<self> */
public function children(): HasMany
/** @return \Illuminate\Database\Eloquent\Relations\HasMany<\Tests\Utils\Models\RoleUserPivot> */
public function roles(): HasMany
{
return $this->hasMany(self::class);
return $this->hasMany(RoleUserPivot::class, 'user_id', 'user_id');
}

/** @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\Tests\Utils\Models\Tag> */
public function tags(): MorphToMany
/** @return \Illuminate\Database\Eloquent\Relations\HasOne<\Tests\Utils\Models\PostStatus> */
public function status(): HasOne
{
return $this->morphToMany(Tag::class, 'taggable');
return $this->hasOne(PostStatus::class);
}

/** @return \Illuminate\Database\Eloquent\Relations\MorphMany<\Tests\Utils\Models\Image> */
public function images(): MorphMany
/** @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Tests\Utils\Models\Task, self> */
public function task(): BelongsTo
{
return $this->morphMany(Image::class, 'imageable');
return $this->belongsTo(Task::class);
}

/** @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Tests\Utils\Models\Category> */
public function categories(): BelongsToMany
/** @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\Tests\Utils\Models\Tag> */
public function tags(): MorphToMany
{
return $this->belongsToMany(Category::class, 'category_post', 'category_id', 'post_id');
return $this->morphToMany(Tag::class, 'taggable');
}

/** @return \Illuminate\Database\Eloquent\Relations\HasMany<\Tests\Utils\Models\RoleUserPivot> */
public function roles(): HasMany
/** @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Tests\Utils\Models\User, self> */
public function user(): BelongsTo
{
return $this->hasMany(RoleUserPivot::class, 'user_id', 'user_id');
return $this->belongsTo(User::class);
}
}
Loading

0 comments on commit d1c8036

Please sign in to comment.