Skip to content

Commit

Permalink
Add withCount support (#920)
Browse files Browse the repository at this point in the history
* Add withCount support

* add comment

* add withCount to transformer

* add resource tests; add transformer tests

* remove unrelated things and focus only on factoryCreate

* check if method exists

* use app version

* last cleanup

* Update TestUserApiResource.php

Remove extra line

---------

Co-authored-by: Sergey Antonets <[email protected]>
Co-authored-by: Shalvah <[email protected]>
  • Loading branch information
3 people authored Jan 18, 2025
1 parent 9f01a1a commit 8a2884e
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/Attributes/ResponseFromApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function __construct(
public ?int $simplePaginate = null,
public ?int $cursorPaginate = null,
public array $additional = [],
public array $withCount = [],
)
{
}
Expand Down
16 changes: 11 additions & 5 deletions src/Extracting/InstantiatesExampleModels.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
];
Expand All @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/TestUserApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Knuckles\Scribe\Tests\Fixtures;

use Illuminate\Foundation\Application;
use Illuminate\Http\Resources\Json\JsonResource;

/**
Expand Down Expand Up @@ -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'];
Expand Down
104 changes: 103 additions & 1 deletion tests/Strategies/Responses/UseResponseAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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" => "[email protected]",
"children" => [
[
"id" => 5,
"name" => "Tested Again",
"email" => "[email protected]",
]
],
],
]),
],
], $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" => "[email protected]",
"children" => [
[
"id" => 5,
"name" => "Tested Again",
"email" => "[email protected]",
]
],
'children_count' => 1,
],
]),
],
], $results);
}

protected function fetch($endpoint, array $documentationConfig = []): array
{
$strategy = new UseResponseAttributes(new DocumentationConfig([]));
return $strategy($endpoint, []);
Expand Down Expand Up @@ -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()
{

}
}

0 comments on commit 8a2884e

Please sign in to comment.