Skip to content

Commit 9fa16f7

Browse files
committed
feat: autoload relations when include
1 parent 51825de commit 9fa16f7

File tree

14 files changed

+153
-27
lines changed

14 files changed

+153
-27
lines changed

src/Descriptors/Relations/Relation.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Ark4ne\JsonApi\Descriptors\Describer;
66
use Ark4ne\JsonApi\Resources\Relationship;
7+
use Ark4ne\JsonApi\Traits\HasRelationLoad;
78
use Ark4ne\JsonApi\Support\Config;
89
use Closure;
910
use Illuminate\Database\Eloquent\Model;
@@ -16,6 +17,8 @@
1617
*/
1718
abstract class Relation extends Describer
1819
{
20+
use HasRelationLoad;
21+
1922
protected ?Closure $links = null;
2023
protected ?Closure $meta = null;
2124
protected bool $whenIncluded;

src/Descriptors/Resolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ trait Resolver
99
{
1010
/**
1111
* @param \Illuminate\Http\Request $request
12-
* @param iterable<mixed>|null $values
12+
* @param iterable<mixed>|null $values
1313
*
1414
* @return array<mixed>|null
1515
*/
@@ -31,6 +31,6 @@ protected function resolveValues(Request $request, ?iterable $values): ?array
3131

3232
return $fields;
3333
}, new Collection)
34-
->toArray();
34+
->all();
3535
}
3636
}

src/Resources/Concerns/Relationships.php

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Ark4ne\JsonApi\Resources\Relationship;
77
use Ark4ne\JsonApi\Support\Includes;
88
use Illuminate\Http\Request;
9+
use Illuminate\Support\Arr;
910
use Illuminate\Support\Collection;
1011

1112
trait Relationships
@@ -34,6 +35,40 @@ protected function toRelationships(Request $request): iterable
3435
return [];
3536
}
3637

38+
/**
39+
* @internal
40+
*
41+
* @return array<string, string|callable>
42+
*/
43+
public function requestedRelationshipsLoad(Request $request): array
44+
{
45+
$schema = self::schema($request);
46+
47+
$walk = static function ($schema) use (&$walk, $request) {
48+
$loads = [];
49+
50+
foreach ($schema->loads as $name => $load) {
51+
if ($load && Includes::include($request, $name)) {
52+
$include = Includes::through($name, static fn() => $walk($schema->relationships[$name]));
53+
foreach ((array)$load as $key => $value) {
54+
if (is_string($value)) {
55+
$loads[$value] = $include;
56+
} elseif (is_string($key)) {
57+
$loads[$key] = $value;
58+
foreach (Arr::dot(Arr::undot($include), "$key.") as $inc => $item) {
59+
$loads[$inc] = $item;
60+
}
61+
}
62+
}
63+
}
64+
}
65+
66+
return $loads;
67+
};
68+
69+
return Arr::dot($walk($schema));
70+
}
71+
3772
/**
3873
* @param \Illuminate\Http\Request $request
3974
*
@@ -63,15 +98,15 @@ private function requestedRelationships(Request $request): array
6398
}
6499

65100
/**
66-
* @param bool $included
67-
* @param \Illuminate\Http\Request $request
101+
* @param bool $included
102+
* @param \Illuminate\Http\Request $request
68103
* @param \Ark4ne\JsonApi\Resources\Relationship $relationship
69104
*
70105
* @return array{data?: mixed, links?: mixed, meta?: mixed}
71106
*/
72107
private function mapRelationship(
73-
bool $included,
74-
Request $request,
108+
bool $included,
109+
Request $request,
75110
Relationship $relationship
76111
): array {
77112
$resource = $relationship->toArray($request, $included);

src/Resources/Concerns/Schema.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Ark4ne\JsonApi\Descriptors\Relations\Relation;
77
use Ark4ne\JsonApi\Descriptors\Resolver;
88
use Ark4ne\JsonApi\Resources\Skeleton;
9+
use Ark4ne\JsonApi\Support\Arr;
910
use Ark4ne\JsonApi\Support\FakeModel;
1011
use Illuminate\Http\Request;
1112
use Illuminate\Support\Collection;
@@ -48,6 +49,23 @@ public static function schema(Request $request = null): Skeleton
4849
$relationship = $relation->getResource();
4950
}
5051
$schema->relationships[$name] = $relationship::schema();
52+
53+
$load = $relation->load();
54+
55+
if ($load === false) {
56+
$schema->loads[$name] = false;
57+
}
58+
elseif ($load === true) {
59+
$schema->loads[$name] = $name;
60+
}
61+
elseif (is_string($load)) {
62+
$schema->loads[$name] = $load;
63+
}
64+
elseif (is_array($load)) {
65+
foreach ($load as $key => $value) {
66+
$schema->loads[$name][$key] = $value;
67+
}
68+
}
5169
}
5270

5371
return self::$schemas[static::class];

src/Resources/JsonApiCollection.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Ark4ne\JsonApi\Resources;
44

55
use Ark4ne\JsonApi\Support\Arr;
6+
use Illuminate\Database\Eloquent\Collection;
7+
use Illuminate\Database\Eloquent\Model;
68
use Illuminate\Http\Resources\Json\JsonResource;
79
use Illuminate\Http\Resources\Json\ResourceCollection;
810

@@ -23,7 +25,7 @@ class JsonApiCollection extends ResourceCollection implements Resourceable
2325
/**
2426
* Create a new anonymous resource collection.
2527
*
26-
* @param mixed $resource
28+
* @param mixed $resource
2729
* @param null|class-string<T> $collects
2830
*
2931
* @return void
@@ -37,12 +39,26 @@ public function __construct($resource, ?string $collects = null)
3739

3840
/**
3941
* @param \Illuminate\Http\Request $request
40-
* @param bool $included
42+
* @param bool $included
4143
*
4244
* @return array<int, array{id: int|string}>
4345
*/
4446
public function toArray(mixed $request, bool $included = true): array
4547
{
48+
if (collect($this->collection)->every(fn($value) => $value instanceof JsonApiResource)) {
49+
$collection = collect($this->collection);
50+
51+
// @phpstan-ignore-next-line
52+
$loads = array_merge(...$collection->map->requestedRelationshipsLoad($request));
53+
54+
// @phpstan-ignore-next-line
55+
$resources = $collection->map->resource;
56+
57+
if (!empty($loads) && $resources->every(fn($resource) => $resource instanceof Model)) {
58+
(new Collection($resources))->loadMissing($loads);
59+
}
60+
}
61+
4662
$data = [];
4763

4864
$base = $this->with;

src/Resources/JsonApiResource.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Ark4ne\JsonApi\Resources\Concerns;
66
use Ark4ne\JsonApi\Descriptors\Descriptors;
77
use Ark4ne\JsonApi\Support\Arr;
8+
use Illuminate\Database\Eloquent\Model;
89
use Illuminate\Http\Resources\Json\JsonResource;
910

1011
/**
@@ -35,6 +36,12 @@ abstract class JsonApiResource extends JsonResource implements Resourceable
3536
*/
3637
public function toArray(mixed $request, bool $included = true): array
3738
{
39+
$loads = $this->requestedRelationshipsLoad($request);
40+
41+
if (!empty($loads) && $this->resource instanceof Model) {
42+
$this->resource->loadMissing($loads);
43+
}
44+
3845
$data = [
3946
'id' => $this->toIdentifier($request),
4047
'type' => $this->toType($request),

src/Resources/Relationship.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Ark4ne\JsonApi\Resources;
44

5+
use Ark4ne\JsonApi\Traits\HasRelationLoad;
56
use Closure;
67
use Illuminate\Http\Resources\Json\JsonResource;
78
use Illuminate\Http\Resources\Json\ResourceCollection;
@@ -16,6 +17,8 @@
1617
*/
1718
class Relationship implements Resourceable
1819
{
20+
use HasRelationLoad;
21+
1922
protected string $relation;
2023

2124
protected bool $asCollection = false;
@@ -24,13 +27,13 @@ class Relationship implements Resourceable
2427

2528
/**
2629
* @param class-string<T> $resource
27-
* @param Closure $value
28-
* @param Closure|null $links
29-
* @param Closure|null $meta
30+
* @param Closure $value
31+
* @param Closure|null $links
32+
* @param Closure|null $meta
3033
*/
3134
public function __construct(
32-
protected string $resource,
33-
protected Closure $value,
35+
protected string $resource,
36+
protected Closure $value,
3437
protected ?Closure $links = null,
3538
protected ?Closure $meta = null
3639
) {
@@ -115,7 +118,7 @@ public function forRelation(string $relation): self
115118

116119
/**
117120
* @param \Illuminate\Http\Request $request
118-
* @param bool $included
121+
* @param bool $included
119122
*
120123
* @return array{data?: array{data?:mixed, links?:mixed, meta?:mixed}, included?: mixed, with?: mixed}
121124
*/

src/Resources/Skeleton.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
class Skeleton
99
{
1010
/**
11-
* @param class-string<T> $for
12-
* @param string $type
13-
* @param string[] $fields
11+
* @param class-string $for
12+
* @param string $type
13+
* @param string[] $fields
1414
* @param array<string, self> $relationships
15+
* @param array<string, false|string|mixed> $loads
1516
*/
1617
public function __construct(
1718
public string $for,
1819
public string $type,
1920
public array $fields = [],
2021
public array $relationships = [],
22+
public array $loads = [],
2123
) {
2224
}
2325
}

src/Traits/HasRelationLoad.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Ark4ne\JsonApi\Traits;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Database\Eloquent\Relations\Relation;
7+
8+
trait HasRelationLoad
9+
{
10+
/** @var bool|string|array<string, callable(Builder|Relation):mixed>|array<array-key, string> */
11+
protected bool|string|array $load = false;
12+
13+
/**
14+
* @param bool|string|array<string, callable(Builder|Relation):mixed>|array<array-key, string> $load
15+
* @return $this
16+
*/
17+
public function withLoad(bool|string|array $load): static
18+
{
19+
$this->load = $load;
20+
return $this;
21+
}
22+
23+
/**
24+
* @return bool|string|array<string, callable(Builder|Relation):mixed>|array<array-key, string>
25+
*/
26+
public function load(): bool|string|array
27+
{
28+
return $this->load;
29+
}
30+
}

tests/Feature/SchemaTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Ark4ne\JsonApi\Resources\JsonApiCollection;
66
use Ark4ne\JsonApi\Resources\Skeleton;
7+
use Illuminate\Database\Eloquent\Builder;
78
use Test\app\Http\Resources\CommentResource;
89
use Test\app\Http\Resources\PostResource;
910
use Test\app\Http\Resources\UserResource;
@@ -18,12 +19,20 @@ public function testSchema()
1819

1920
$user->relationships['posts'] = $post;
2021
$user->relationships['comments'] = $comment;
22+
$user->loads['posts'] = 'posts';
23+
$user->loads['comments'] = [
24+
'comments' => fn(Builder $q) => $q->where('content', 'like', '%e%')
25+
];
2126

2227
$post->relationships['user'] = $user;
2328
$post->relationships['comments'] = $comment;
29+
$post->loads['user'] = 'user';
30+
$post->loads['comments'] = 'comments';
2431

2532
$comment->relationships['user'] = $user;
2633
$comment->relationships['post'] = $post;
34+
$comment->loads['user'] = 'user';
35+
$comment->loads['post'] = 'post';
2736

2837
$this->assertEquals($user, UserResource::schema());
2938
$this->assertEquals($post, PostResource::schema());

0 commit comments

Comments
 (0)