diff --git a/src/Attributes/ResponseFromApiResource.php b/src/Attributes/ResponseFromApiResource.php index a36e7dbc..80f37006 100644 --- a/src/Attributes/ResponseFromApiResource.php +++ b/src/Attributes/ResponseFromApiResource.php @@ -24,6 +24,7 @@ public function __construct( public ?int $simplePaginate = null, public ?int $cursorPaginate = null, public array $additional = [], + public array $withCount = [], ) { } diff --git a/src/Extracting/InstantiatesExampleModels.php b/src/Extracting/InstantiatesExampleModels.php index 8a73fabd..1f17b59f 100644 --- a/src/Extracting/InstantiatesExampleModels.php +++ b/src/Extracting/InstantiatesExampleModels.php @@ -21,7 +21,7 @@ trait InstantiatesExampleModels */ protected function instantiateExampleModel( ?string $type = null, array $factoryStates = [], - array $relations = [], ?ReflectionFunctionAbstract $transformationMethod = null + array $relations = [], ?ReflectionFunctionAbstract $transformationMethod = null, array $withCount = [], ) { // If the API Resource uses an empty resource, there won't be an example model @@ -42,7 +42,7 @@ protected function instantiateExampleModel( $configuredStrategies = $this->config->get('examples.models_source', ['factoryCreate', 'factoryMake', 'databaseFirst']); $strategies = [ - 'factoryCreate' => fn() => $this->getExampleModelFromFactoryCreate($type, $factoryStates, $relations), + 'factoryCreate' => fn() => $this->getExampleModelFromFactoryCreate($type, $factoryStates, $relations, $withCount), 'factoryMake' => fn() => $this->getExampleModelFromFactoryMake($type, $factoryStates, $relations), 'databaseFirst' => fn() => $this->getExampleModelFromDatabaseFirst($type, $relations), ]; @@ -63,13 +63,19 @@ protected function instantiateExampleModel( /** * @param class-string $type * @param string[] $factoryStates + * @param string[] $relations + * @param string[] $withCount * * @return \Illuminate\Database\Eloquent\Model|null */ - protected function getExampleModelFromFactoryCreate(string $type, array $factoryStates = [], array $relations = []) + protected function getExampleModelFromFactoryCreate(string $type, array $factoryStates = [], array $relations = [], array $withCount = []) { - $factory = Utils::getModelFactory($type, $factoryStates, $relations); - return $factory->create()->refresh()->load($relations); + // Since $relations and $withCount refer to the same underlying relationships in the model, + // combining them ensures that all required relationships are initialized when passed to the factory. + $allRelations = array_unique(array_merge($relations, $withCount)); + + $factory = Utils::getModelFactory($type, $factoryStates, $allRelations); + return $factory->create()->refresh()->load($relations)->loadCount($withCount); } /** diff --git a/src/Extracting/Strategies/Responses/UseResponseAttributes.php b/src/Extracting/Strategies/Responses/UseResponseAttributes.php index 9e19d40d..9fc7534f 100644 --- a/src/Extracting/Strategies/Responses/UseResponseAttributes.php +++ b/src/Extracting/Strategies/Responses/UseResponseAttributes.php @@ -60,7 +60,7 @@ protected function getApiResourceResponse(ResponseFromApiResource $attributeInst ); $modelInstantiator = null; } else { - $modelInstantiator = fn() => $this->instantiateExampleModel($modelToBeTransformed, $attributeInstance->factoryStates, $attributeInstance->with); + $modelInstantiator = fn() => $this->instantiateExampleModel($modelToBeTransformed, $attributeInstance->factoryStates, $attributeInstance->with, null, $attributeInstance->withCount); } $pagination = []; diff --git a/tests/Fixtures/TestUserApiResource.php b/tests/Fixtures/TestUserApiResource.php index 0fe5d8b5..5380eef3 100644 --- a/tests/Fixtures/TestUserApiResource.php +++ b/tests/Fixtures/TestUserApiResource.php @@ -2,6 +2,7 @@ namespace Knuckles\Scribe\Tests\Fixtures; +use Illuminate\Foundation\Application; use Illuminate\Http\Resources\Json\JsonResource; /** @@ -34,6 +35,10 @@ public function toArray($request) }), ]; + if (version_compare(Application::VERSION, '9', '>=')) { + $result['children_count'] = $this->whenCounted('children_count'); + } + if ($this['state1'] && $this['random-state']) { $result['state1'] = $this['state1']; $result['random-state'] = $this['random-state']; diff --git a/tests/Strategies/Responses/UseResponseAttributesTest.php b/tests/Strategies/Responses/UseResponseAttributesTest.php index 18a98390..8e1693c2 100644 --- a/tests/Strategies/Responses/UseResponseAttributesTest.php +++ b/tests/Strategies/Responses/UseResponseAttributesTest.php @@ -2,8 +2,10 @@ namespace Knuckles\Scribe\Tests\Strategies\Responses; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Foundation\Application; use Illuminate\Routing\Route; +use Illuminate\Support\Facades\Schema; use Knuckles\Camel\Extraction\ExtractedEndpointData; use Knuckles\Scribe\Attributes\Response; use Knuckles\Scribe\Attributes\ResponseFromApiResource; @@ -285,7 +287,95 @@ public function can_parse_apiresource_attributes_with_cursor_pagination() ], $results); } - protected function fetch($endpoint): array + /** @test */ + public function can_parse_apiresource_attributes_and_load_children_using_factory_create() + { + Schema::create('test_users', function (Blueprint $table) { + $table->id(); + $table->string('first_name'); + $table->string('last_name'); + $table->string('email'); + $table->integer('parent_id')->nullable(); + }); + + $factory = app(\Illuminate\Database\Eloquent\Factory::class); + $factory->afterCreating(TestUser::class, function (TestUser $user, $faker) { + if ($user->id === 4) { + Utils::getModelFactory(TestUser::class)->create(['id' => 5, 'parent_id' => 4]); + } + }); + $documentationConfig = ['examples' => ['models_source' => ['factoryCreate']]]; + + $results = $this->fetch($this->endpoint("apiResourceAttributesIncludeChildren"), $documentationConfig); + $this->assertArraySubset([ + [ + 'status' => 200, + 'content' => json_encode([ + "data" => [ + "id" => 4, + "name" => "Tested Again", + "email" => "a@b.com", + "children" => [ + [ + "id" => 5, + "name" => "Tested Again", + "email" => "a@b.com", + ] + ], + ], + ]), + ], + ], $results); + } + + + /** @test */ + public function can_parse_apiresource_attributes_and_load_children_and_children_count_using_factory_create() + { + if (version_compare(Application::VERSION, '9', '<')) { + $this->markTestSkipped('The whenCounted method in JsonResource requires Laravel 9 or higher.'); + } + + Schema::create('test_users', function (Blueprint $table) { + $table->id(); + $table->string('first_name'); + $table->string('last_name'); + $table->string('email'); + $table->integer('parent_id')->nullable(); + }); + + $factory = app(\Illuminate\Database\Eloquent\Factory::class); + $factory->afterCreating(TestUser::class, function (TestUser $user, $faker) { + if ($user->id === 4) { + Utils::getModelFactory(TestUser::class)->create(['id' => 5, 'parent_id' => 4]); + } + }); + $documentationConfig = ['examples' => ['models_source' => ['factoryCreate']]]; + + $results = $this->fetch($this->endpoint("apiResourceAttributesIncludeChildrenAndChildrenCount"), $documentationConfig); + $this->assertArraySubset([ + [ + 'status' => 200, + 'content' => json_encode([ + "data" => [ + "id" => 4, + "name" => "Tested Again", + "email" => "a@b.com", + "children" => [ + [ + "id" => 5, + "name" => "Tested Again", + "email" => "a@b.com", + ] + ], + 'children_count' => 1, + ], + ]), + ], + ], $results); + } + + protected function fetch($endpoint, array $documentationConfig = []): array { $strategy = new UseResponseAttributes(new DocumentationConfig([])); return $strategy($endpoint, []); @@ -345,4 +435,16 @@ public function apiResourceAttributesWithCursorPaginate() { } + + #[ResponseFromApiResource(TestUserApiResource::class, with: ['children'], withCount: ['children'])] + public function apiResourceAttributesIncludeChildrenAndChildrenCount() + { + + } + + #[ResponseFromApiResource(TestUserApiResource::class, with: ['children'])] + public function apiResourceAttributesIncludeChildren() + { + + } }