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),
- ];
- }
-}