瀏覽代碼

Add withCount support (#920)

* 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 <sergey.antonets@talkremit.com>
Co-authored-by: Shalvah <shalvah@users.noreply.github.com>
Serge 3 月之前
父節點
當前提交
8a2884e1a2

+ 1 - 0
src/Attributes/ResponseFromApiResource.php

@@ -24,6 +24,7 @@ class ResponseFromApiResource
         public ?int $simplePaginate = null,
         public ?int $cursorPaginate = null,
         public array $additional = [],
+        public array $withCount = [],
     )
     {
     }

+ 11 - 5
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 @@ trait InstantiatesExampleModels
         $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 @@ trait InstantiatesExampleModels
     /**
      * @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);
     }
 
     /**

+ 1 - 1
src/Extracting/Strategies/Responses/UseResponseAttributes.php

@@ -60,7 +60,7 @@ class UseResponseAttributes extends PhpAttributeStrategy
             );
             $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 = [];

+ 5 - 0
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 @@ class TestUserApiResource extends JsonResource
             }),
         ];
 
+        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'];

+ 103 - 1
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 @@ class UseResponseAttributesTest extends BaseLaravelTest
         ], $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 @@ class ResponseAttributesTestController
     {
 
     }
+
+    #[ResponseFromApiResource(TestUserApiResource::class, with: ['children'], withCount: ['children'])]
+    public function apiResourceAttributesIncludeChildrenAndChildrenCount()
+    {
+
+    }
+
+    #[ResponseFromApiResource(TestUserApiResource::class, with: ['children'])]
+    public function apiResourceAttributesIncludeChildren()
+    {
+
+    }
 }