Browse Source

Feat nested relations

Tomasz Kisiel 3 years ago
parent
commit
1c4e9f9dc4

+ 25 - 4
src/Tools/Utils.php

@@ -2,10 +2,13 @@
 
 namespace Knuckles\Scribe\Tools;
 
+use _PHPStan_76800bfb5\Nette\PhpGenerator\PhpFile;
 use Closure;
 use DirectoryIterator;
 use Exception;
 use FastRoute\RouteParser\Std;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Routing\Route;
 use Illuminate\Support\Str;
 use Knuckles\Scribe\Exceptions\CouldntFindFactory;
@@ -190,13 +193,31 @@ class Utils
             /** @var \Illuminate\Database\Eloquent\Factories\Factory $factory */
             $factory = call_user_func_array([$modelName, 'factory'], []);
             foreach ($states as $state) {
-                $factory = $factory->$state();
+                if (method_exists(get_class($factory), $state)) {
+                    $factory = $factory->$state();
+                }
             }
 
             foreach ($relations as $relation) {
-                // Eg "posts" relation becomes hasPosts() method
-                $methodName = "has$relation";
-                $factory = $factory->$methodName();
+                $relationChain = explode('.', $relation);
+                $relationVector = array_shift($relationChain);
+
+                $relationModel = get_class((new $modelName())->{$relationVector}()->getModel());
+                $relationType = get_class((new $modelName())->{$relationVector}());
+
+                $factoryChain = empty($relationChain)
+                    ? call_user_func_array([$relationModel, 'factory'], [])
+                    : Utils::getModelFactory($relationModel, $states, $relationChain);
+
+                if ($relationType === BelongsToMany::class) {
+                    $pivot = method_exists($factory, 'pivot' . $relationVector)
+                        ? $factory->{'pivot' . $relationVector}()
+                        : [];
+
+                    $factory = $factory->hasAttached($factoryChain, $pivot, $relationVector);
+                } else {
+                    $factory = $factory->has($factoryChain, $relationVector);
+                }
             }
         } else {
             try {

+ 14 - 0
tests/Fixtures/TestPet.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Knuckles\Scribe\Tests\Fixtures;
+
+use Illuminate\Database\Eloquent\Model;
+
+class TestPet extends Model
+{
+
+    public function owners()
+    {
+        return $this->belongsToMany(TestUser::class)->withPivot('duration');
+    }
+}

+ 32 - 0
tests/Fixtures/TestPetApiResource.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Knuckles\Scribe\Tests\Fixtures;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class TestPetApiResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     *
+     * @return array
+     */
+    public function toArray($request)
+    {
+        $result = [
+            'id' => $this->id,
+            'name' => $this->name,
+            'species' => $this->species,
+            'owners' => $this->whenLoaded('owners', function () {
+                return TestUserApiResource::collection($this->owners);
+            }),
+            'ownership' => $this->whenPivotLoaded('pet_user', function () {
+                return $this->pivot;
+            })
+        ];
+
+        return $result;
+    }
+}

+ 27 - 0
tests/Fixtures/TestPetApiResourceCollection.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Knuckles\Scribe\Tests\Fixtures;
+
+use Illuminate\Http\Resources\Json\ResourceCollection;
+
+class TestPetApiResourceCollection extends ResourceCollection
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     *
+     * @return array
+     */
+    public function toArray($request)
+    {
+        $data = [
+            'data' => $this->collection,
+            'links' => [
+                'self' => 'link-value',
+            ],
+        ];
+
+        return $data;
+    }
+}

+ 5 - 0
tests/Fixtures/TestUser.php

@@ -11,4 +11,9 @@ class TestUser extends Model
     {
         return $this->hasMany(TestUser::class, 'parent_id');
     }
+
+    public function pets()
+    {
+        return $this->belongsToMany(TestPet::class)->withPivot('duration');
+    }
 }

+ 3 - 0
tests/Fixtures/TestUserApiResource.php

@@ -22,6 +22,9 @@ class TestUserApiResource extends JsonResource
             'children' => $this->whenLoaded('children', function () {
                 return TestUserApiResource::collection($this->children);
             }),
+            'pets' => $this->whenLoaded('pets', function () {
+                return TestPetApiResource::collection($this->pets);
+            }),
         ];
 
         if($request->route()->named('someone')) {

+ 211 - 0
tests/Strategies/Responses/UseApiResourceTagsTest.php

@@ -8,6 +8,7 @@ use Knuckles\Scribe\Extracting\Strategies\Responses\UseApiResourceTags;
 use Knuckles\Scribe\ScribeServiceProvider;
 use Knuckles\Scribe\Tests\BaseLaravelTest;
 use Knuckles\Scribe\Tests\Fixtures\TestController;
+use Knuckles\Scribe\Tests\Fixtures\TestPet;
 use Knuckles\Scribe\Tests\Fixtures\TestUser;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 use Knuckles\Scribe\Tools\Utils;
@@ -49,6 +50,13 @@ class UseApiResourceTagsTest extends BaseLaravelTest
         });
         $factory->state(TestUser::class, 'state1', ["state1" => true]);
         $factory->state(TestUser::class, 'random-state', ["random-state" => true]);
+        $factory->define(TestPet::class, function () {
+            return [
+                'id' => 1,
+                'name' => 'Mephistopheles',
+                'species' => 'dog',
+            ];
+        });
     }
 
     /** @test */
@@ -221,6 +229,209 @@ class UseApiResourceTagsTest extends BaseLaravelTest
         ], $results);
     }
 
+    /** @test */
+    public function loads_specified_nested_relations_for_generated_model()
+    {
+        $factory = app(\Illuminate\Database\Eloquent\Factory::class);
+        $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
+            if ($user->id === 4) {
+                $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]);
+                $user->setRelation('children', collect([$child]));
+
+                $grandchild = Utils::getModelFactory(TestUser::class)->make(['id' => 6, 'parent_id' => 5]);
+                $child->setRelation('children', collect([$grandchild]));
+            }
+        });
+
+        $config = new DocumentationConfig([]);
+
+        $route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);
+
+        $strategy = new UseApiResourceTags($config);
+        $tags = [
+            new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'),
+            new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser with=children.children'),
+        ];
+        $results = $strategy->getApiResourceResponse($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));
+
+        $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' => [
+                                    [
+                                        'id' => 6,
+                                        'name' => 'Tested Again',
+                                        'email' => 'a@b.com',
+                                    ]
+                                ]
+                            ],
+                        ],
+                    ],
+                ]),
+            ],
+        ], $results);
+    }
+
+    /** @test */
+    public function loads_specified_many_to_many_relations_for_generated_model()
+    {
+        $factory = app(\Illuminate\Database\Eloquent\Factory::class);
+        $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
+            $pet = Utils::getModelFactory(TestPet::class)->make(['id' => 1]);
+            $user->setRelation('pets', collect([$pet]));
+        });
+
+        $config = new DocumentationConfig([]);
+
+        $route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);
+
+        $strategy = new UseApiResourceTags($config);
+        $tags = [
+            new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'),
+            new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser with=pets'),
+        ];
+        $results = $strategy->getApiResourceResponse($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));
+
+        $this->assertArraySubset([
+            [
+                'status' => 200,
+                'content' => json_encode([
+                    'data' => [
+                        'id' => 4,
+                        'name' => 'Tested Again',
+                        'email' => 'a@b.com',
+                        'pets' => [
+                            [
+                                'id' => 1,
+                                'name' => 'Mephistopheles',
+                                'species' => 'dog'
+                            ],
+                        ],
+                    ],
+                ]),
+            ],
+        ], $results);
+    }
+
+    /** @test */
+    public function loads_specified_many_to_many_and_nested_relations_for_generated_model()
+    {
+        $factory = app(\Illuminate\Database\Eloquent\Factory::class);
+        $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
+            if ($user->id === 4) {
+                $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]);
+                $user->setRelation('children', collect([$child]));
+
+                $pet = Utils::getModelFactory(TestPet::class)->make(['id' => 1]);
+                $child->setRelation('pets', collect([$pet]));
+            }
+        });
+
+        $config = new DocumentationConfig([]);
+
+        $route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);
+
+        $strategy = new UseApiResourceTags($config);
+        $tags = [
+            new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'),
+            new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser with=children.pets'),
+        ];
+        $results = $strategy->getApiResourceResponse($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));
+
+        $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',
+                                'pets' => [
+                                    [
+                                        'id' => 1,
+                                        'name' => 'Mephistopheles',
+                                        'species' => 'dog'
+                                    ],
+                                ],
+                            ]
+                        ]
+
+                    ],
+                ]),
+            ],
+        ], $results);
+    }
+
+    /** @test */
+    public function loads_specified_many_to_many_relations_for_generated_model_with_pivot()
+    {
+        $factory = app(\Illuminate\Database\Eloquent\Factory::class);
+        $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
+            $pet = Utils::getModelFactory(TestPet::class)->make(['id' => 1]);
+
+            $pivot = $pet->newPivot($user, [
+                'pet_id' => $pet->id,
+                'user_id' => $user->id,
+                'duration' => 2
+            ], 'pet_user', true);
+
+            $pet->setRelation('pivot', $pivot);
+
+            $user->setRelation('pets', collect([$pet]));
+        });
+
+        $config = new DocumentationConfig([]);
+
+        $route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);
+
+        $strategy = new UseApiResourceTags($config);
+        $tags = [
+            new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'),
+            new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser with=pets'),
+        ];
+        $results = $strategy->getApiResourceResponse($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));
+
+        $this->assertArraySubset([
+            [
+                'status' => 200,
+                'content' => json_encode([
+                    'data' => [
+                        'id' => 4,
+                        'name' => 'Tested Again',
+                        'email' => 'a@b.com',
+                        'pets' => [
+                            [
+                                'id' => 1,
+                                'name' => 'Mephistopheles',
+                                'species' => 'dog',
+                                'ownership' => [
+                                    'pet_id' => 1,
+                                    'user_id' => 4,
+                                    'duration' => 2
+                                ]
+                            ],
+                        ],
+                    ],
+                ]),
+            ],
+        ], $results);
+    }
+
     /** @test */
     public function can_parse_apiresourcecollection_tags()
     {