Skip to content

Commit

Permalink
Feat nested relations
Browse files Browse the repository at this point in the history
  • Loading branch information
kishieel committed Nov 11, 2021
1 parent fff9c29 commit 802c53d
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 4 deletions.
28 changes: 24 additions & 4 deletions src/Tools/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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;
Expand Down Expand Up @@ -190,13 +192,31 @@ public static function getModelFactory(string $modelName, array $states = [], ar
/** @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 === HasMany::class) {
$factory = $factory->has($factoryChain, $relationVector);
} elseif ($relationType === BelongsToMany::class) {
$pivot = method_exists($factory, 'pivot' . $relationVector)
? $factory->{'pivot' . $relationVector}()
: [];

$factory = $factory->hasAttached($factoryChain, $pivot, $relationVector);
}
}
} else {
try {
Expand Down
14 changes: 14 additions & 0 deletions tests/Fixtures/TestPet.php
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions tests/Fixtures/TestPetApiResource.php
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions tests/Fixtures/TestPetApiResourceCollection.php
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions tests/Fixtures/TestUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ public function children()
{
return $this->hasMany(TestUser::class, 'parent_id');
}

public function pets()
{
return $this->belongsToMany(TestPet::class)->withPivot('duration');
}
}
3 changes: 3 additions & 0 deletions tests/Fixtures/TestUserApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public function toArray($request)
'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')) {
Expand Down
211 changes: 211 additions & 0 deletions tests/Strategies/Responses/UseApiResourceTagsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -49,6 +50,13 @@ public function setUp(): void
});
$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 */
Expand Down Expand Up @@ -221,6 +229,209 @@ public function loads_specified_relations_for_generated_model()
], $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()
{
Expand Down

0 comments on commit 802c53d

Please sign in to comment.