Skip to content

Commit 5d6e3f9

Browse files
committed
feat: struct value type
1 parent 7459b7d commit 5d6e3f9

20 files changed

+206
-34
lines changed

src/Descriptors/Describer.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,12 @@ abstract public function retriever(): null|string|Closure;
151151
*
152152
* @return int|string
153153
*/
154-
public static function retrieveName(mixed $value, int|string $key): int|string
154+
public static function retrieveName(mixed $value, int|string $key, null|string $prefix = null): int|string
155155
{
156156
if (is_int($key) && $value instanceof self && is_string($retriever = $value->retriever())) {
157-
return $retriever;
157+
return $prefix ? $prefix . '.' . $retriever : $retriever;
158158
}
159159

160-
return $key;
160+
return $prefix ? $prefix . '.' . $key : $key;
161161
}
162162
}

src/Descriptors/Values.php

+15-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
namespace Ark4ne\JsonApi\Descriptors;
44

5-
use Ark4ne\JsonApi\Descriptors\Relations\RelationMany;
6-
use Ark4ne\JsonApi\Descriptors\Relations\RelationOne;
7-
use Ark4ne\JsonApi\Descriptors\Values\{ValueArray,
5+
use Ark4ne\JsonApi\Descriptors\Values\{
6+
ValueArray,
87
ValueBool,
98
ValueDate,
109
ValueEnum,
1110
ValueFloat,
1211
ValueInteger,
1312
ValueMixed,
14-
ValueString};
13+
ValueString,
14+
ValueStruct
15+
};
1516
use Closure;
1617

1718
/**
@@ -98,4 +99,14 @@ protected function enum(null|string|Closure $attribute = null): ValueEnum
9899
{
99100
return new ValueEnum($attribute);
100101
}
102+
103+
/**
104+
* @param Closure(T):iterable<string, \Closure|mixed>|iterable<array-key, \Ark4ne\JsonApi\Descriptors\Values\Value> $attribute
105+
*
106+
* @return \Ark4ne\JsonApi\Descriptors\Values\ValueStruct<T>
107+
*/
108+
protected function struct(Closure $attribute = null): ValueStruct
109+
{
110+
return new ValueStruct($attribute);
111+
}
101112
}

src/Descriptors/Values/Value.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public function resolveFor(Request $request, mixed $model, string $field): mixed
108108

109109
return $value === null && $this->nullable
110110
? null
111-
: $this->value($value);
111+
: $this->value($value, $request);
112112
}
113113

114114
protected function check(Request $request, mixed $model, string $attribute): bool
@@ -120,5 +120,5 @@ protected function check(Request $request, mixed $model, string $attribute): boo
120120
return parent::check($request, $model, $attribute);
121121
}
122122

123-
abstract protected function value(mixed $of): mixed;
123+
abstract protected function value(mixed $of, Request $request): mixed;
124124
}

src/Descriptors/Values/ValueArray.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Ark4ne\JsonApi\Descriptors\Values;
44

5+
use Illuminate\Http\Request;
56
use Illuminate\Support\Collection;
67

78
/**
@@ -12,10 +13,10 @@ class ValueArray extends Value
1213
{
1314
/**
1415
* @param mixed $of
15-
*
16+
* @param Request $request
1617
* @return array<array-key, mixed>
1718
*/
18-
public function value(mixed $of): array
19+
public function value(mixed $of, Request $request): array
1920
{
2021
return (new Collection($of))->toArray();
2122
}

src/Descriptors/Values/ValueBool.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace Ark4ne\JsonApi\Descriptors\Values;
44

5+
use Illuminate\Http\Request;
6+
57
/**
68
* @template T
79
* @extends Value<T>
810
*/
911
class ValueBool extends Value
1012
{
11-
public function value(mixed $of): bool
13+
public function value(mixed $of, Request $request): bool
1214
{
1315
return (bool)$of;
1416
}

src/Descriptors/Values/ValueDate.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Closure;
77
use DateTime;
88
use DateTimeInterface;
9+
use Illuminate\Http\Request;
910

1011
/**
1112
* @template T
@@ -31,7 +32,7 @@ public function format(string $format): static
3132
/**
3233
* @throws \Exception
3334
*/
34-
public function value(mixed $of): string
35+
public function value(mixed $of, Request $request): string
3536
{
3637
if ($of === null) {
3738
return (new DateTime("@0"))->format($this->format);

src/Descriptors/Values/ValueEnum.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Ark4ne\JsonApi\Descriptors\Values;
44

55
use BackedEnum;
6+
use Illuminate\Http\Request;
67
use UnitEnum;
78

89
/**
@@ -11,7 +12,7 @@
1112
*/
1213
class ValueEnum extends Value
1314
{
14-
protected function value(mixed $of): mixed
15+
protected function value(mixed $of, Request $request): mixed
1516
{
1617
if ($of instanceof BackedEnum) return $of->value;
1718
if ($of instanceof UnitEnum) return $of->name;

src/Descriptors/Values/ValueFloat.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Ark4ne\JsonApi\Support\Config;
66
use Closure;
7+
use Illuminate\Http\Request;
78

89
/**
910
* @template T
@@ -26,7 +27,7 @@ public function precision(int $precision): static
2627
return $this;
2728
}
2829

29-
public function value(mixed $of): float
30+
public function value(mixed $of, Request $request): float
3031
{
3132
if (isset($this->precision)) {
3233
$precision = 10 ** $this->precision;

src/Descriptors/Values/ValueInteger.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace Ark4ne\JsonApi\Descriptors\Values;
44

5+
use Illuminate\Http\Request;
6+
57
/**
68
* @template T
79
* @extends Value<T>
810
*/
911
class ValueInteger extends Value
1012
{
11-
public function value(mixed $of): int
13+
public function value(mixed $of, Request $request): int
1214
{
1315
return (int)$of;
1416
}

src/Descriptors/Values/ValueMixed.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace Ark4ne\JsonApi\Descriptors\Values;
44

5+
use Illuminate\Http\Request;
6+
57
/**
68
* @template T
79
* @extends Value<T>
810
*/
911
class ValueMixed extends Value
1012
{
11-
public function value(mixed $of): mixed
13+
public function value(mixed $of, Request $request): mixed
1214
{
1315
return $of;
1416
}

src/Descriptors/Values/ValueString.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace Ark4ne\JsonApi\Descriptors\Values;
44

5+
use Illuminate\Http\Request;
6+
57
/**
68
* @template T
79
* @extends Value<T>
810
*/
911
class ValueString extends Value
1012
{
11-
public function value(mixed $of): string
13+
public function value(mixed $of, Request $request): string
1214
{
1315
return (string)$of;
1416
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Ark4ne\JsonApi\Descriptors\Values;
4+
5+
use Ark4ne\JsonApi\Descriptors\Resolver;
6+
use Ark4ne\JsonApi\Support\Values;
7+
use Closure;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Collection;
10+
11+
/**
12+
* @template T
13+
* @extends Value<T>
14+
*/
15+
class ValueStruct extends Value
16+
{
17+
use Resolver;
18+
19+
protected mixed $resource;
20+
21+
public function __construct(Closure $values)
22+
{
23+
parent::__construct($values);
24+
}
25+
26+
public function resolveFor(Request $request, mixed $model, string $field): mixed
27+
{
28+
$this->resource = $model;
29+
30+
return parent::resolveFor($request, $model, $field);
31+
}
32+
33+
/**
34+
* @param mixed $of
35+
* @param Request $request
36+
*
37+
* @return array<array-key, mixed>
38+
*/
39+
public function value(mixed $of, Request $request): array
40+
{
41+
$attributes = Values::mergeValues($this->resolveValues($request, Values::mergeValues($of)));
42+
43+
return (new Collection($attributes))->toArray();
44+
}
45+
}

src/Resources/Concerns/Attributes.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Ark4ne\JsonApi\Resources\Concerns;
44

5+
use Ark4ne\JsonApi\Support\Arr;
56
use Ark4ne\JsonApi\Support\Fields;
67
use Illuminate\Http\Request;
78

@@ -58,7 +59,7 @@ private function requestedAttributes(Request $request): array
5859

5960
$attributes = null === $fields
6061
? $attributes
61-
: array_intersect_key($attributes, array_fill_keys($fields, true));
62+
: Arr::intersectKeyStruct($attributes, Arr::undot(array_fill_keys($fields, true)));
6263

6364
return array_map('\value', $attributes);
6465
});

src/Resources/Concerns/PrepareData.php

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ trait PrepareData
2828
*/
2929
protected function mergeValues(iterable $data): iterable
3030
{
31-
3231
return Values::mergeValues($data);
3332
}
3433

src/Resources/Concerns/Schema.php

+25-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Ark4ne\JsonApi\Descriptors\Describer;
66
use Ark4ne\JsonApi\Descriptors\Relations\Relation;
77
use Ark4ne\JsonApi\Descriptors\Resolver;
8+
use Ark4ne\JsonApi\Descriptors\Values\ValueStruct;
89
use Ark4ne\JsonApi\Resources\Skeleton;
910
use Ark4ne\JsonApi\Support\FakeModel;
1011
use Ark4ne\JsonApi\Support\Values;
@@ -37,9 +38,16 @@ public static function schema(null|Request $request = null): Skeleton
3738
);
3839

3940
$schema->fields = (new Collection(Values::mergeValues($resource->toAttributes($request))))
40-
->map(fn($value, $key) => Describer::retrieveName($value, $key))
41-
->values()
42-
->all();
41+
->flatMap(function ($value, $key) use ($resource) {
42+
$collection = (new Collection([Describer::retrieveName($value, $key) => true]));
43+
44+
if ($value instanceof ValueStruct) {
45+
return $collection->merge(self::structFields($resource, $key, $value, $key));
46+
}
47+
48+
return $collection;
49+
})
50+
->keys()->all();
4351

4452
foreach (Values::mergeValues($resource->toRelationships($request)) as $name => $relation) {
4553
if ($relation instanceof Relation) {
@@ -83,4 +91,18 @@ private static function new(): static
8391

8492
return $instance;
8593
}
94+
95+
private static function structFields(mixed $resource, string $key, ValueStruct $struct, null|string $prefix = null): Collection
96+
{
97+
return (new Collection(Values::mergeValues(($struct->retriever())($resource, $key))))
98+
->flatMap(function($value, $key) use ($resource, $prefix) {
99+
$prefixed = Describer::retrieveName($value, $key, $prefix);
100+
101+
if ($value instanceof ValueStruct) {
102+
return self::structFields($resource, $key, $value, $prefixed);
103+
}
104+
105+
return [$prefixed => $value];
106+
});
107+
}
86108
}

src/Support/Arr.php

+22
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,28 @@ public static function apply(array &$array, string $path, mixed $value, null|str
163163
return $array;
164164
}
165165

166+
/**
167+
* @template TKey as array-key
168+
* @template TValue
169+
*
170+
* @param array<TKey, TValue> $array
171+
* @param array<array-key, mixed> $struct
172+
*
173+
* @return array<TKey, TValue>
174+
*/
175+
public static function intersectKeyStruct(array $array, array $struct): array
176+
{
177+
$res = array_intersect_key($array, $struct);
178+
179+
foreach ($res as $key => $value) {
180+
if (is_array($value) && is_array($struct[$key])) {
181+
$res[$key] = self::intersectKeyStruct($value, $struct[$key]);
182+
}
183+
}
184+
185+
return $res;
186+
}
187+
166188
/**
167189
* @template TKey as array-key
168190
* @template TValue

tests/Feature/SchemaTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ public function testSchema()
2020
'with-apply-conditional-raw',
2121
'with-apply-conditional-closure',
2222
'with-apply-conditional-value',
23+
'struct-set',
24+
'struct-set.name',
25+
'struct-set.email',
26+
'struct-set.casted',
27+
'struct-set.with-apply-conditional-raw',
28+
'struct-set.closure',
29+
'struct-set.missing',
30+
'struct-set.sub-struct.int',
31+
'struct-set.sub-struct.float',
32+
'struct-set.third-struct.int',
33+
'struct-set.third-struct.float',
2334
]);
2435
$post = new Skeleton(PostResource::class, 'post', ['state', 'title', 'content']);
2536
$comment = new Skeleton(CommentResource::class, 'comment', ['content']);

0 commit comments

Comments
 (0)