diff --git a/framework/core/src/Extend/ApiResource.php b/framework/core/src/Extend/ApiResource.php index bdd682de99..2c6634e112 100644 --- a/framework/core/src/Extend/ApiResource.php +++ b/framework/core/src/Extend/ApiResource.php @@ -25,6 +25,8 @@ class ApiResource implements ExtenderInterface private array $removeEndpoints = []; private array $endpoint = []; private array $fields = []; + private array $fieldsBefore = []; + private array $fieldsAfter = []; private array $removeFields = []; private array $field = []; private array $sorts = []; @@ -93,6 +95,32 @@ public function fields(callable|string $fields): self return $this; } + /** + * Add fields to the resource before a certain field. + * + * @param string $before the name of the field to add the new fields before. + * @param callable|class-string $fields must be a callable that returns an array of objects that implement \Tobyz\JsonApiServer\Schema\Field. + */ + public function fieldsBefore(string $before, callable|string $fields): self + { + $this->fieldsBefore[] = [$before, $fields]; + + return $this; + } + + /** + * Add fields to the resource after a certain field. + * + * @param string $after the name of the field to add the new fields after. + * @param callable|class-string $fields must be a callable that returns an array of objects that implement \Tobyz\JsonApiServer\Schema\Field. + */ + public function fieldsAfter(string $after, callable|string $fields): self + { + $this->fieldsAfter[] = [$after, $fields]; + + return $this; + } + /** * Remove fields from the resource. * @@ -221,6 +249,26 @@ function (array $endpoints, Resource $resource) use ($container): array { $fields = array_merge($fields, $newFieldsCallback()); } + foreach ($this->fieldsBefore as [$before, $newFieldsCallback]) { + $newFieldsCallback = ContainerUtil::wrapCallback($newFieldsCallback, $container); + $newFields = $newFieldsCallback(); + $beforeIndex = array_search($before, array_column($fields, 'name')); + + if ($beforeIndex !== false) { + array_splice($fields, $beforeIndex, 0, $newFields); + } + } + + foreach ($this->fieldsAfter as [$after, $newFieldsCallback]) { + $newFieldsCallback = ContainerUtil::wrapCallback($newFieldsCallback, $container); + $newFields = $newFieldsCallback(); + $afterIndex = array_search($after, array_column($fields, 'name')); + + if ($afterIndex !== false) { + array_splice($fields, $afterIndex + 1, 0, $newFields); + } + } + foreach ($this->removeFields as $field) { [$fieldsToRemove, $condition] = $field; diff --git a/framework/core/tests/integration/extenders/ApiControllerTest.php b/framework/core/tests/integration/extenders/ApiResourceTest.php similarity index 65% rename from framework/core/tests/integration/extenders/ApiControllerTest.php rename to framework/core/tests/integration/extenders/ApiResourceTest.php index 340ecde5f8..783fd363cf 100644 --- a/framework/core/tests/integration/extenders/ApiControllerTest.php +++ b/framework/core/tests/integration/extenders/ApiResourceTest.php @@ -15,8 +15,9 @@ use Flarum\Api\Endpoint\Show; use Flarum\Api\Resource\AbstractDatabaseResource; use Flarum\Api\Resource\DiscussionResource; +use Flarum\Api\Resource\ForumResource; use Flarum\Api\Resource\UserResource; -use Flarum\Api\Schema\Relationship\ToMany; +use Flarum\Api\Schema; use Flarum\Api\Sort\SortColumn; use Flarum\Discussion\Discussion; use Flarum\Extend; @@ -29,7 +30,7 @@ use PHPUnit\Framework\Attributes\Test; use Tobyz\JsonApiServer\Schema\Field\Field; -class ApiControllerTest extends TestCase +class ApiResourceTest extends TestCase { use RetrievesAuthorizedUsers; @@ -48,12 +49,18 @@ protected function setUp(): void ['id' => 1, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], ['id' => 2, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 3, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], ['id' => 3, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], + + ['id' => 4, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], + ['id' => 5, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], + ['id' => 6, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], ], Post::class => [ ['id' => 1, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '

can i haz relationz?

'], ['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '

can i haz relationz?

'], ['id' => 3, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '

can i haz relationz?

'], ['id' => 3, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'discussionRenamed', 'content' => '

can i haz relationz?

'], + + ['id' => 5, 'discussion_id' => 6, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '

can i haz relationz?

'], ], ]); } @@ -243,7 +250,7 @@ public function custom_relationship_included_if_added() ->hasMany('customApiControllerRelation', Discussion::class, 'user_id'), (new Extend\ApiResource(UserResource::class)) ->fields(fn () => [ - ToMany::make('customApiControllerRelation') + Schema\Relationship\ToMany::make('customApiControllerRelation') ->type('discussions') ->includable(), ]) @@ -271,7 +278,7 @@ public function custom_relationship_optionally_included_if_added() ->hasMany('customApiControllerRelation2', Discussion::class, 'user_id'), (new Extend\ApiResource(UserResource::class)) ->fields(fn () => [ - ToMany::make('customApiControllerRelation2') + Schema\Relationship\ToMany::make('customApiControllerRelation2') ->type('discussions') ->includable(), ]) @@ -333,7 +340,7 @@ public function custom_relationship_not_optionally_included_if_removed() ->hasMany('customApiControllerRelation2', Discussion::class, 'user_id'), (new Extend\ApiResource(UserResource::class)) ->fields(fn () => [ - ToMany::make('customApiControllerRelation2') + Schema\Relationship\ToMany::make('customApiControllerRelation2') ->type('discussions') ->includable(), ]) @@ -362,7 +369,7 @@ public function custom_limit_doesnt_work_by_default() $payload = json_decode($response->getBody()->getContents(), true); - $this->assertCount(3, $payload['data']); + $this->assertCount(6, $payload['data']); } #[Test] @@ -444,7 +451,7 @@ public function custom_sort_field_exists_if_added() $payload = json_decode($body = $response->getBody()->getContents(), true); $this->assertEquals(200, $response->getStatusCode(), $body); - $this->assertEquals([3, 1, 2], Arr::pluck($payload['data'], 'id')); + $this->assertEquals([3, 1, 4, 5, 6, 2], Arr::pluck($payload['data'], 'id')); } #[Test] @@ -502,7 +509,7 @@ public function custom_sort_field_works_if_set() $payload = json_decode($response->getBody()->getContents(), true); $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals([2, 1, 3], Arr::pluck($payload['data'], 'id')); + $this->assertEquals([2, 1, 4, 5, 6, 3], Arr::pluck($payload['data'], 'id')); } #[Test] @@ -709,6 +716,326 @@ public function custom_callable_second_level_relation_is_loaded_if_added() $this->assertFalse($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isEmpty()); } + + #[Test] + public function custom_attributes_dont_exist_by_default() + { + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayNotHasKey('customAttribute', $payload['data']['attributes']); + } + + #[Test] + public function custom_attributes_exist_if_added() + { + $this->extend( + (new Extend\ApiResource(ForumResource::class)) + ->fields(fn () => [ + Schema\Boolean::make('customAttribute') + ->get(fn () => true), + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']); + } + + #[Test] + public function custom_attributes_exist_if_added_before_field() + { + $this->extend( + (new Extend\ApiResource(DiscussionResource::class)) + ->fieldsBefore('title', fn () => [ + Schema\Boolean::make('customAttribute') + ->writable() + ->set(fn (Discussion $discussion) => $this->assertNull($discussion->title)) + ]) + ); + + $response = $this->send( + $this->request('POST', '/api/discussions', [ + 'authenticatedAs' => 1, + 'json' => [ + 'data' => [ + 'type' => 'discussions', + 'attributes' => [ + 'title' => 'Custom Discussion Title', + 'customAttribute' => true, + 'content' => 'Custom Discussion Content', + ], + ], + ], + ]) + ); + + $this->assertEquals(201, $response->getStatusCode(), $response->getBody()->getContents()); + } + + #[Test] + public function custom_attributes_exist_if_added_after_field() + { + $this->extend( + (new Extend\ApiResource(DiscussionResource::class)) + ->fieldsAfter('title', fn () => [ + Schema\Boolean::make('customAttribute') + ->writable() + ->set(fn (Discussion $discussion) => $this->assertNotNull($discussion->title)) + ]) + ); + + $response = $this->send( + $this->request('POST', '/api/discussions', [ + 'authenticatedAs' => 1, + 'json' => [ + 'data' => [ + 'type' => 'discussions', + 'attributes' => [ + 'title' => 'Custom Discussion Title', + 'customAttribute' => true, + 'content' => 'Custom Discussion Content', + ], + ], + ], + ]) + ); + + $this->assertEquals(201, $response->getStatusCode(), $response->getBody()->getContents()); + } + + #[Test] + public function custom_attributes_with_invokable_exist_if_added() + { + $this->extend( + (new Extend\ApiResource(ForumResource::class)) + ->fields(CustomAttributesInvokableClass::class) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('customAttributeFromInvokable', $payload['data']['attributes']); + } + + #[Test] + public function custom_attributes_exist_if_added_to_parent_class() + { + $this->extend( + (new Extend\ApiResource(AbstractDatabaseResource::class)) + ->fields(fn () => [ + Schema\Boolean::make('customAttribute') + ->get(fn () => true), + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']); + } + + #[Test] + public function custom_attributes_prioritize_child_classes() + { + $this->extend( + (new Extend\ApiResource(AbstractDatabaseResource::class)) + ->fields(fn () => [ + Schema\Str::make('customAttribute') + ->get(fn () => 'initialValue') + ]), + (new Extend\ApiResource(UserResource::class)) + ->fields(fn () => [ + Schema\Str::make('customAttribute') + ->get(fn () => 'newValue') + ]), + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']); + $this->assertEquals('newValue', $payload['data']['attributes']['customAttribute']); + } + + #[Test] + public function custom_attributes_can_be_overridden() + { + $this->extend( + (new Extend\ApiResource(UserResource::class)) + ->fields(fn () => [ + Schema\Str::make('someCustomAttribute') + ->get(fn () => 'newValue'), + ]) + ->fields(fn () => [ + Schema\Str::make('someCustomAttribute') + ->get(fn () => 'secondValue'), + Schema\Str::make('someOtherCustomAttribute') + ->get(fn () => 'secondValue'), + ]) + ->fields(fn () => [ + Schema\Str::make('someOtherCustomAttribute') + ->get(fn () => 'newValue'), + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('someCustomAttribute', $payload['data']['attributes']); + $this->assertEquals('secondValue', $payload['data']['attributes']['someCustomAttribute']); + $this->assertArrayHasKey('someOtherCustomAttribute', $payload['data']['attributes']); + $this->assertEquals('newValue', $payload['data']['attributes']['someOtherCustomAttribute']); + } + + #[Test] + public function custom_relations_dont_exist_by_default() + { + $this->extend( + (new Extend\ApiResource(UserResource::class)) + ->endpoint(Show::class, function (Show $endpoint): Show { + return $endpoint->addDefaultInclude(['customSerializerRelation', 'postCustomRelation', 'anotherCustomRelation']); + }) + ); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $this->assertEquals(400, $response->getStatusCode()); + } + + #[Test] + public function custom_hasMany_relationship_exists_if_added() + { + $this->extend( + (new Extend\Model(User::class)) + ->hasMany('customSerializerRelation', Discussion::class, 'user_id'), + (new Extend\ApiResource(UserResource::class)) + ->fields(fn () => [ + Schema\Relationship\ToMany::make('customSerializerRelation') + ->type('discussions') + ->includable() + ]) + ->endpoint(Show::class, function (Show $endpoint) { + return $endpoint->addDefaultInclude(['customSerializerRelation']); + }) + ); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $responseJson = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customSerializerRelation', $responseJson['data']['relationships']); + $this->assertCount(4, $responseJson['data']['relationships']['customSerializerRelation']['data']); + } + + #[Test] + public function custom_hasOne_relationship_exists_if_added() + { + $this->extend( + (new Extend\Model(User::class)) + ->hasOne('customSerializerRelation', Discussion::class, 'user_id'), + (new Extend\ApiResource(UserResource::class)) + ->fields(fn () => [ + Schema\Relationship\ToOne::make('customSerializerRelation') + ->type('discussions') + ->includable() + ]) + ->endpoint(Show::class, function (Show $endpoint) { + return $endpoint->addDefaultInclude(['customSerializerRelation']); + }) + ); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $responseJson = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customSerializerRelation', $responseJson['data']['relationships']); + $this->assertEquals('discussions', $responseJson['data']['relationships']['customSerializerRelation']['data']['type']); + } + + #[Test] + public function custom_relationship_is_inherited_to_child_classes() + { + $this->extend( + (new Extend\Model(User::class)) + ->hasMany('anotherCustomRelation', Discussion::class, 'user_id'), + (new Extend\ApiResource(AbstractDatabaseResource::class)) + ->fields(fn () => [ + Schema\Relationship\ToMany::make('anotherCustomRelation') + ->type('discussions') + ->includable() + ]) + ->endpoint(Show::class, function (Show $endpoint) { + return $endpoint->addDefaultInclude(['anotherCustomRelation']); + }) + ); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $responseJson = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('anotherCustomRelation', $responseJson['data']['relationships']); + $this->assertCount(4, $responseJson['data']['relationships']['anotherCustomRelation']['data']); + } } class CustomAfterEndpointInvokableClass @@ -720,3 +1047,14 @@ public function __invoke(Context $context, Discussion $discussion): Discussion return $discussion; } } + +class CustomAttributesInvokableClass +{ + public function __invoke(): array + { + return [ + Schema\Boolean::make('customAttributeFromInvokable') + ->get(fn () => true), + ]; + } +} diff --git a/framework/core/tests/integration/extenders/ApiSerializerTest.php b/framework/core/tests/integration/extenders/ApiSerializerTest.php deleted file mode 100644 index 1ec6d367c3..0000000000 --- a/framework/core/tests/integration/extenders/ApiSerializerTest.php +++ /dev/null @@ -1,320 +0,0 @@ -prepareDatabase([ - User::class => [ - $this->normalUser() - ], - Discussion::class => [ - ['id' => 1, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], - ['id' => 2, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], - ['id' => 3, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], - ], - Post::class => [ - ['id' => 1, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '

can i haz relationz?

'], - ], - ]); - } - - #[Test] - public function custom_attributes_dont_exist_by_default() - { - $this->app(); - - $response = $this->send( - $this->request('GET', '/api', [ - 'authenticatedAs' => 1, - ]) - ); - - $payload = json_decode($response->getBody()->getContents(), true); - - $this->assertArrayNotHasKey('customAttribute', $payload['data']['attributes']); - } - - #[Test] - public function custom_attributes_exist_if_added() - { - $this->extend( - (new Extend\ApiResource(ForumResource::class)) - ->fields(fn () => [ - Schema\Boolean::make('customAttribute') - ->get(fn () => true), - ]) - ); - - $this->app(); - - $response = $this->send( - $this->request('GET', '/api', [ - 'authenticatedAs' => 1, - ]) - ); - - $payload = json_decode($response->getBody()->getContents(), true); - - $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']); - } - - #[Test] - public function custom_attributes_with_invokable_exist_if_added() - { - $this->extend( - (new Extend\ApiResource(ForumResource::class)) - ->fields(CustomAttributesInvokableClass::class) - ); - - $this->app(); - - $response = $this->send( - $this->request('GET', '/api', [ - 'authenticatedAs' => 1, - ]) - ); - - $payload = json_decode($response->getBody()->getContents(), true); - - $this->assertArrayHasKey('customAttributeFromInvokable', $payload['data']['attributes']); - } - - #[Test] - public function custom_attributes_exist_if_added_to_parent_class() - { - $this->extend( - (new Extend\ApiResource(AbstractDatabaseResource::class)) - ->fields(fn () => [ - Schema\Boolean::make('customAttribute') - ->get(fn () => true), - ]) - ); - - $this->app(); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $payload = json_decode($response->getBody()->getContents(), true); - - $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']); - } - - #[Test] - public function custom_attributes_prioritize_child_classes() - { - $this->extend( - (new Extend\ApiResource(AbstractDatabaseResource::class)) - ->fields(fn () => [ - Schema\Str::make('customAttribute') - ->get(fn () => 'initialValue') - ]), - (new Extend\ApiResource(UserResource::class)) - ->fields(fn () => [ - Schema\Str::make('customAttribute') - ->get(fn () => 'newValue') - ]), - ); - - $this->app(); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $payload = json_decode($response->getBody()->getContents(), true); - - $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']); - $this->assertEquals('newValue', $payload['data']['attributes']['customAttribute']); - } - - #[Test] - public function custom_attributes_can_be_overridden() - { - $this->extend( - (new Extend\ApiResource(UserResource::class)) - ->fields(fn () => [ - Schema\Str::make('someCustomAttribute') - ->get(fn () => 'newValue'), - ]) - ->fields(fn () => [ - Schema\Str::make('someCustomAttribute') - ->get(fn () => 'secondValue'), - Schema\Str::make('someOtherCustomAttribute') - ->get(fn () => 'secondValue'), - ]) - ->fields(fn () => [ - Schema\Str::make('someOtherCustomAttribute') - ->get(fn () => 'newValue'), - ]) - ); - - $this->app(); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $payload = json_decode($response->getBody()->getContents(), true); - - $this->assertArrayHasKey('someCustomAttribute', $payload['data']['attributes']); - $this->assertEquals('secondValue', $payload['data']['attributes']['someCustomAttribute']); - $this->assertArrayHasKey('someOtherCustomAttribute', $payload['data']['attributes']); - $this->assertEquals('newValue', $payload['data']['attributes']['someOtherCustomAttribute']); - } - - #[Test] - public function custom_relations_dont_exist_by_default() - { - $this->extend( - (new Extend\ApiResource(UserResource::class)) - ->endpoint(Show::class, function (Show $endpoint): Show { - return $endpoint->addDefaultInclude(['customSerializerRelation', 'postCustomRelation', 'anotherCustomRelation']); - }) - ); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $this->assertEquals(400, $response->getStatusCode()); - } - - #[Test] - public function custom_hasMany_relationship_exists_if_added() - { - $this->extend( - (new Extend\Model(User::class)) - ->hasMany('customSerializerRelation', Discussion::class, 'user_id'), - (new Extend\ApiResource(UserResource::class)) - ->fields(fn () => [ - Schema\Relationship\ToMany::make('customSerializerRelation') - ->type('discussions') - ->includable() - ]) - ->endpoint(Show::class, function (Show $endpoint) { - return $endpoint->addDefaultInclude(['customSerializerRelation']); - }) - ); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $responseJson = json_decode($response->getBody(), true); - - $this->assertArrayHasKey('customSerializerRelation', $responseJson['data']['relationships']); - $this->assertCount(3, $responseJson['data']['relationships']['customSerializerRelation']['data']); - } - - #[Test] - public function custom_hasOne_relationship_exists_if_added() - { - $this->extend( - (new Extend\Model(User::class)) - ->hasOne('customSerializerRelation', Discussion::class, 'user_id'), - (new Extend\ApiResource(UserResource::class)) - ->fields(fn () => [ - Schema\Relationship\ToOne::make('customSerializerRelation') - ->type('discussions') - ->includable() - ]) - ->endpoint(Show::class, function (Show $endpoint) { - return $endpoint->addDefaultInclude(['customSerializerRelation']); - }) - ); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $responseJson = json_decode($response->getBody(), true); - - $this->assertArrayHasKey('customSerializerRelation', $responseJson['data']['relationships']); - $this->assertEquals('discussions', $responseJson['data']['relationships']['customSerializerRelation']['data']['type']); - } - - #[Test] - public function custom_relationship_is_inherited_to_child_classes() - { - $this->extend( - (new Extend\Model(User::class)) - ->hasMany('anotherCustomRelation', Discussion::class, 'user_id'), - (new Extend\ApiResource(AbstractDatabaseResource::class)) - ->fields(fn () => [ - Schema\Relationship\ToMany::make('anotherCustomRelation') - ->type('discussions') - ->includable() - ]) - ->endpoint(Show::class, function (Show $endpoint) { - return $endpoint->addDefaultInclude(['anotherCustomRelation']); - }) - ); - - $response = $this->send( - $this->request('GET', '/api/users/2', [ - 'authenticatedAs' => 1, - ]) - ); - - $responseJson = json_decode($response->getBody(), true); - - $this->assertArrayHasKey('anotherCustomRelation', $responseJson['data']['relationships']); - $this->assertCount(3, $responseJson['data']['relationships']['anotherCustomRelation']['data']); - } -} - -class CustomAttributesInvokableClass -{ - public function __invoke(): array - { - return [ - Schema\Boolean::make('customAttributeFromInvokable') - ->get(fn () => true), - ]; - } -}