Skip to content

Commit e78b155

Browse files
committed
Fix including deeply nested relationships
1 parent 5ba3f16 commit e78b155

File tree

2 files changed

+172
-2
lines changed

2 files changed

+172
-2
lines changed

src/Concerns/Relationships.php

+21-2
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ public static function guessRelationshipResourceUsing(callable|null $callback)
5353
*/
5454
public function withIncludePrefix(string $prefix)
5555
{
56-
$this->includePrefix = "{$this->includePrefix}{$prefix}.";
56+
$this->includePrefix = self::joinIncludes($this->includePrefix, $prefix);
5757

5858
return $this;
5959
}
6060

61+
6162
/**
6263
* @internal
6364
*
@@ -117,7 +118,9 @@ private function resolveInclude(mixed $resource, string $prefix)
117118
{
118119
return match (true) {
119120
$resource instanceof PotentiallyMissing && $resource->isMissing() => null,
120-
$resource instanceof JsonApiResource || $resource instanceof JsonApiResourceCollection => $resource->withIncludePrefix($prefix),
121+
$resource instanceof JsonApiResource || $resource instanceof JsonApiResourceCollection => $resource->withIncludePrefix(
122+
self::joinIncludes($this->includePrefix, $prefix)
123+
),
121124
default => throw UnknownRelationshipException::from($resource),
122125
};
123126
}
@@ -198,4 +201,20 @@ private static function guessRelationshipResource(string $relationship, JsonApiR
198201
throw new RuntimeException('Unable to guess the resource class for relationship ['.$value.'] for ['.$resource::class.'].');
199202
})($relationship, $resource);
200203
}
204+
205+
/**
206+
* @internal
207+
*/
208+
private static function joinIncludes(string $start, string $finish): string
209+
{
210+
$prefix = '';
211+
212+
if ($start !== '') {
213+
$prefix = Str::finish($start, '.');
214+
}
215+
216+
$prefix .= Str::finish($finish, '.');
217+
218+
return $prefix;
219+
}
201220
}

tests/Feature/RelationshipsTest.php

+151
Original file line numberDiff line numberDiff line change
@@ -1401,4 +1401,155 @@ public function toRelationships($request): array
14011401
]);
14021402
$this->assertValidJsonApi($response);
14031403
}
1404+
1405+
public function testItCanIncludeDeepNestedResourcesForASingleResource(): void
1406+
{
1407+
$post = new BasicModel([
1408+
'id' => 'post-id',
1409+
'title' => 'post-title',
1410+
'content' => 'post-content',
1411+
]);
1412+
$post->author = new BasicModel([
1413+
'id' => 'author-id',
1414+
'name' => 'author-name',
1415+
]);
1416+
$post->author->license = new BasicModel([
1417+
'id' => 'license-id',
1418+
'key' => 'license-key',
1419+
]);
1420+
$post->author->license->user = new BasicModel([
1421+
'id' => 'user-id',
1422+
'name' => 'Average Joe',
1423+
]);
1424+
$post->author->license->user->posts = Collection::make([
1425+
new BasicModel([
1426+
'id' => 'nested-post-id',
1427+
'title' => 'Hello world!',
1428+
]),
1429+
]);
1430+
$post->author->license->user->posts[0]->author = new BasicModel([
1431+
'id' => 'nested-post-author-id',
1432+
'name' => 'Tim Mac',
1433+
]);
1434+
$post->author->license->user->posts[0]->comments = Collection::make([
1435+
new BasicModel([
1436+
'id' => 'nested-post-comment-id',
1437+
'content' => 'Oh hey there!',
1438+
]),
1439+
]);
1440+
Route::get('test-route', fn () => PostResource::make($post));
1441+
1442+
$response = $this->getJson('test-route?include=author.license.user.posts.comments,author.license.user.posts.author');
1443+
1444+
$response->assertOk();
1445+
$response->assertExactJson([
1446+
'data' => [
1447+
'id' => 'post-id',
1448+
'type' => 'basicModels',
1449+
'attributes' => [
1450+
'title' => 'post-title',
1451+
'content' => 'post-content',
1452+
],
1453+
'relationships' => [
1454+
'author' => [
1455+
'data' => [
1456+
'id' => 'author-id',
1457+
'type' => 'basicModels',
1458+
],
1459+
],
1460+
],
1461+
],
1462+
'jsonapi' => [
1463+
'version' => '1.0',
1464+
],
1465+
'included' => [
1466+
[
1467+
'id' => 'author-id',
1468+
'type' => 'basicModels',
1469+
'attributes' => [
1470+
'name' => 'author-name',
1471+
],
1472+
'relationships' => [
1473+
'license' => [
1474+
'data' => [
1475+
'id' => 'license-id',
1476+
'type' => 'basicModels',
1477+
],
1478+
],
1479+
],
1480+
],
1481+
[
1482+
'id' => 'license-id',
1483+
'type' => 'basicModels',
1484+
'attributes' => [
1485+
'key' => 'license-key',
1486+
],
1487+
'relationships' => [
1488+
'user' => [
1489+
'data' => [
1490+
'id' => 'user-id',
1491+
'type' => 'basicModels',
1492+
],
1493+
],
1494+
],
1495+
],
1496+
[
1497+
'id' => 'user-id',
1498+
'type' => 'basicModels',
1499+
'attributes' => [
1500+
'name' => 'Average Joe',
1501+
],
1502+
'relationships' => [
1503+
'posts' => [
1504+
'data' => [
1505+
[
1506+
'id' => 'nested-post-id',
1507+
'type' => 'basicModels',
1508+
]
1509+
]
1510+
]
1511+
]
1512+
],
1513+
[
1514+
'id' => 'nested-post-id',
1515+
'type' => 'basicModels',
1516+
'attributes' => [
1517+
'title' => 'Hello world!',
1518+
'content' => null,
1519+
],
1520+
'relationships' => [
1521+
'author' => [
1522+
'data' => [
1523+
'id' => 'nested-post-author-id',
1524+
'type' => 'basicModels',
1525+
]
1526+
],
1527+
'comments' => [
1528+
'data' => [
1529+
[
1530+
'id' => 'nested-post-comment-id',
1531+
'type' => 'basicModels',
1532+
]
1533+
]
1534+
]
1535+
]
1536+
],
1537+
[
1538+
'id' => 'nested-post-author-id',
1539+
'type' => 'basicModels',
1540+
'attributes' => [
1541+
'name' => 'Tim Mac',
1542+
],
1543+
],
1544+
[
1545+
'id' => 'nested-post-comment-id',
1546+
'type' => 'basicModels',
1547+
'attributes' => [
1548+
'content' => 'Oh hey there!',
1549+
],
1550+
]
1551+
],
1552+
]);
1553+
$this->assertValidJsonApi($response);
1554+
}
14041555
}

0 commit comments

Comments
 (0)