Skip to content

Commit 52afebf

Browse files
committed
Adds basic trait
1 parent 48b1415 commit 52afebf

File tree

2 files changed

+421
-0
lines changed

2 files changed

+421
-0
lines changed

src/Util/ValueObjectTrait.php

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Storyblok\Api\Util;
6+
7+
use BackedEnum as T;
8+
use OskarStark\Value\TrimmedNonEmptyString;
9+
use Safe\DateTimeImmutable;
10+
use Storyblok\Api\Domain\Type\Asset;
11+
use Storyblok\Api\Domain\Type\Editable;
12+
use Storyblok\Api\Domain\Type\MultiLink;
13+
use Storyblok\Api\Domain\Type\RichText;
14+
use Storyblok\Api\Domain\Value\Uuid;
15+
use Tiptap\Editor;
16+
use Webmozart\Assert\Assert;
17+
18+
trait ValueObjectTrait
19+
{
20+
/**
21+
* @template T of object
22+
*
23+
* @param array<mixed> $values
24+
* @param non-empty-string $key
25+
* @param class-string<T> $class
26+
*
27+
* @return T
28+
*/
29+
final protected static function one(array $values, string $key, string $class): object
30+
{
31+
Assert::keyExists($values, $key);
32+
Assert::isList($values[$key]);
33+
Assert::count($values[$key], 1);
34+
35+
return new $class($values[$key][0]);
36+
}
37+
38+
/**
39+
* @template T of object
40+
*
41+
* @param array<mixed> $values
42+
* @param class-string<T> $class
43+
*
44+
* @return list<T>
45+
*/
46+
final protected static function list(array $values, string $key, string $class, ?int $min = null, ?int $max = null, ?int $count = null): array
47+
{
48+
if (null !== $count && (null !== $min || null !== $max)) {
49+
throw new \InvalidArgumentException('You can not use $count with $min or $max.');
50+
}
51+
52+
if (null !== $count) {
53+
Assert::keyExists($values, $key);
54+
Assert::count($values[$key], $count);
55+
}
56+
57+
if (null !== $min) {
58+
Assert::keyExists($values, $key);
59+
Assert::minCount($values[$key], $min);
60+
} else {
61+
if (!\array_key_exists($key, $values)) {
62+
return [];
63+
}
64+
}
65+
66+
Assert::isList($values[$key]);
67+
68+
if (null !== $max) {
69+
Assert::maxCount($values[$key], $max);
70+
}
71+
72+
return array_map(static fn (array $item) => new $class($item), $values[$key]);
73+
}
74+
75+
/**
76+
* @template T of \BackedEnum
77+
*
78+
* @param array<mixed> $values
79+
* @param non-empty-string $key
80+
* @param class-string<T> $class
81+
* @param null|T $default
82+
* @param null|array<T> $allowedSubset Only some cases of the enum that are allowed
83+
*
84+
* @return T
85+
*/
86+
final protected static function enum(array $values, string $key, string $class, ?\BackedEnum $default = null, ?array $allowedSubset = null): \BackedEnum
87+
{
88+
Assert::keyExists($values, $key);
89+
90+
if (!enum_exists($class)) {
91+
throw new \InvalidArgumentException(\sprintf('The class "%s" is not an enum.', $class));
92+
}
93+
94+
try {
95+
$enum = $class::from($values[$key]);
96+
97+
if (\is_array($allowedSubset)
98+
&& [] !== $allowedSubset
99+
) {
100+
if (!method_exists($enum, 'equalsOneOf')) {
101+
throw new \InvalidArgumentException(\sprintf(
102+
'The enum "%s" does not have the method "equalsOneOf", but an allowed subset is defined.',
103+
$class,
104+
));
105+
}
106+
107+
Assert::true($enum->equalsOneOf($allowedSubset));
108+
}
109+
110+
return $enum;
111+
} catch (\ValueError $e) {
112+
if (null !== $default) {
113+
return $default;
114+
}
115+
116+
throw $e;
117+
}
118+
}
119+
120+
/**
121+
* @param array<mixed> $values
122+
* @param non-empty-string $key
123+
*/
124+
final protected static function DateTimeImmutable(array $values, string $key, ?\DateTimeZone $timezone = null): DateTimeImmutable
125+
{
126+
Assert::keyExists($values, $key);
127+
128+
return new DateTimeImmutable($values[$key], $timezone);
129+
}
130+
131+
/**
132+
* @param array<mixed> $values
133+
* @param non-empty-string $key
134+
*/
135+
final protected static function Uuid(array $values, string $key): Uuid
136+
{
137+
return new Uuid(self::string($values, $key));
138+
}
139+
140+
/**
141+
* @param array<mixed> $values
142+
* @param non-empty-string $key
143+
*/
144+
final protected static function MultiLink(array $values, string $key): MultiLink
145+
{
146+
Assert::keyExists($values, $key);
147+
Assert::isArray($values[$key]);
148+
149+
return new MultiLink($values[$key]);
150+
}
151+
152+
/**
153+
* @param array<mixed> $values
154+
* @param non-empty-string $key
155+
*/
156+
final protected static function Asset(array $values, string $key): Asset
157+
{
158+
Assert::keyExists($values, $key);
159+
Assert::isArray($values[$key]);
160+
161+
return new Asset($values[$key]);
162+
}
163+
164+
/**
165+
* @param array<mixed> $values
166+
* @param non-empty-string $key
167+
*/
168+
final protected static function RichText(array $values, string $key): RichText
169+
{
170+
Assert::keyExists($values, $key);
171+
Assert::isArray($values[$key]);
172+
173+
return new RichText($values[$key]);
174+
}
175+
176+
/**
177+
* @param array<mixed> $values
178+
* @param non-empty-string $key
179+
*/
180+
final protected static function nullOrRichText(array $values, string $key): ?RichText
181+
{
182+
$value = null;
183+
184+
if (\array_key_exists($key, $values)
185+
&& (null !== $values[$key] && [] !== $values[$key])
186+
) {
187+
Assert::isArray($values[$key]);
188+
$value = new RichText($values[$key]);
189+
190+
// Check if ueberdosis/tiptap-php package is installed if not we can not properly check if the RichText
191+
// object is empty. Sometimes the API delivers a valid RichText JSON with one or many empty paragraphs.
192+
if (!class_exists(Editor::class)) {
193+
return $value;
194+
}
195+
196+
$text = \trim((new Editor())->setContent($value->toArray())->getText());
197+
198+
if ('' === $text) {
199+
$value = null;
200+
}
201+
}
202+
203+
return $value;
204+
}
205+
206+
/**
207+
* @param array<mixed> $values
208+
* @param non-empty-string $key
209+
*/
210+
final protected static function zeroOrInteger(array $values, string $key): int
211+
{
212+
$value = 0;
213+
214+
if (\array_key_exists($key, $values)
215+
&& [] !== $values[$key]
216+
) {
217+
$value = (int) $values[$key];
218+
}
219+
220+
return $value;
221+
}
222+
223+
/**
224+
* @param array<mixed> $values
225+
* @param non-empty-string $key
226+
*/
227+
final protected static function zeroOrFloat(array $values, string $key): float
228+
{
229+
$value = 0.0;
230+
231+
if (\array_key_exists($key, $values) && [] !== $values[$key]) {
232+
$value = (float) $values[$key];
233+
}
234+
235+
return $value;
236+
}
237+
238+
/**
239+
* @template T of object
240+
*
241+
* @param array<mixed> $values
242+
* @param non-empty-string $key
243+
* @param class-string<T> $class
244+
*
245+
* @return null|T
246+
*/
247+
final protected static function nullOrOne(array $values, string $key, string $class): ?object
248+
{
249+
if (\array_key_exists($key, $values)
250+
&& \count($values[$key]) > 0
251+
) {
252+
Assert::count($values[$key], 1);
253+
Assert::isList($values[$key]);
254+
255+
return new $class($values[$key][0]);
256+
}
257+
258+
return null;
259+
}
260+
261+
/**
262+
* @param array<mixed> $values
263+
* @param non-empty-string $key
264+
*/
265+
final protected static function boolean(array $values, string $key): bool
266+
{
267+
$value = false;
268+
269+
if (\array_key_exists($key, $values)) {
270+
$value = true === $values[$key];
271+
}
272+
273+
return $value;
274+
}
275+
276+
/**
277+
* @param array<mixed> $values
278+
* @param non-empty-string $key
279+
*/
280+
final protected static function nullOrAsset(array $values, string $key): ?Asset
281+
{
282+
if (!\array_key_exists($key, $values)) {
283+
return null;
284+
}
285+
286+
try {
287+
return new Asset($values[$key]);
288+
} catch (\InvalidArgumentException|\TypeError) {
289+
return null;
290+
}
291+
}
292+
293+
/**
294+
* @param array<mixed> $values
295+
* @param non-empty-string $key
296+
*/
297+
final protected static function nullOrMultiLink(array $values, string $key): ?MultiLink
298+
{
299+
if (!\array_key_exists($key, $values)) {
300+
return null;
301+
}
302+
303+
$linkValues = $values[$key];
304+
305+
// If the link url and id are empty, we return null
306+
if ((!isset($linkValues['url']) || '' === trim($linkValues['url']))
307+
&& (!isset($linkValues['id']) || '' === trim($linkValues['id']))
308+
) {
309+
return null;
310+
}
311+
312+
try {
313+
return new MultiLink($linkValues);
314+
} catch (\InvalidArgumentException|\TypeError) {
315+
return null;
316+
}
317+
}
318+
319+
/**
320+
* Returns null if the value is not set or a trimmed non-empty string.
321+
*
322+
* @param array<mixed> $values
323+
* @param non-empty-string $key
324+
*/
325+
final protected static function nullOrString(array $values, string $key): ?string
326+
{
327+
if (!\array_key_exists($key, $values)) {
328+
return null;
329+
}
330+
331+
try {
332+
return TrimmedNonEmptyString::from($values[$key])->toString();
333+
} catch (\InvalidArgumentException) {
334+
return null;
335+
}
336+
}
337+
338+
/**
339+
* Returns a trimmed non-empty string.
340+
*
341+
* @param array<mixed> $values
342+
* @param non-empty-string $key
343+
*/
344+
final protected static function string(array $values, string $key, ?int $maxLength = null): string
345+
{
346+
Assert::keyExists($values, $key);
347+
348+
if (null !== $maxLength) {
349+
Assert::maxLength($values[$key], $maxLength);
350+
}
351+
352+
return TrimmedNonEmptyString::fromString($values[$key])->toString();
353+
}
354+
355+
/**
356+
* Returns null if the value is not set or an Editable.
357+
*
358+
* @param array<mixed> $values
359+
* @param non-empty-string $key
360+
*/
361+
final protected static function nullOrEditable(array $values, string $key): ?Editable
362+
{
363+
if (!\array_key_exists($key, $values)) {
364+
return null;
365+
}
366+
367+
try {
368+
return new Editable($values[$key]);
369+
} catch (\InvalidArgumentException) {
370+
return null;
371+
}
372+
}
373+
}

0 commit comments

Comments
 (0)