Skip to content

Commit abc0e79

Browse files
GromNaNdg
authored andcommitted
Support union type in Type::nullable (#141)
1 parent 0106e52 commit abc0e79

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

readme.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,8 @@ Each type or union/intersection type can be passed as a string, you can also use
538538
use Nette\PhpGenerator\Type;
539539

540540
$member->setType('array'); // or Type::Array;
541-
$member->setType('array|string'); // or Type::union('array', 'string')
541+
$member->setType('?array'); // or Type::nullable(Type::Array);
542+
$member->setType('array|string'); // or Type::union(Type::Array, Type::String)
542543
$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
543544
$member->setType(null); // removes type
544545
```

src/PhpGenerator/Type.php

+21-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace Nette\PhpGenerator;
1111

12+
use Nette;
13+
1214

1315
/**
1416
* PHP return, property and parameter types.
@@ -85,7 +87,25 @@ class Type
8587

8688
public static function nullable(string $type, bool $nullable = true): string
8789
{
88-
return ($nullable ? '?' : '') . ltrim($type, '?');
90+
if (str_contains($type, '&')) {
91+
return $nullable
92+
? throw new Nette\InvalidArgumentException('Intersection types cannot be nullable.')
93+
: $type;
94+
}
95+
96+
$nnType = preg_replace('#^\?|^null\||\|null(?=\||$)#i', '', $type);
97+
$always = (bool) preg_match('#^(null|mixed)$#i', $nnType);
98+
if ($nullable) {
99+
return match (true) {
100+
$always, $type !== $nnType => $type,
101+
str_contains($type, '|') => $type . '|null',
102+
default => '?' . $type,
103+
};
104+
} else {
105+
return $always
106+
? throw new Nette\InvalidArgumentException("Type $type cannot be not nullable.")
107+
: $nnType;
108+
}
89109
}
90110

91111

tests/PhpGenerator/Type.phpt

+44-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,50 @@ use Nette\PhpGenerator\Type;
66
use Tester\Assert;
77
require __DIR__ . '/../bootstrap.php';
88

9+
// Nullable
10+
Assert::same('?int', Type::nullable(Type::Int));
11+
Assert::same('int', Type::nullable(Type::Int, nullable: false));
912

10-
Assert::same('A|string', Type::union(A::class, Type::String));
13+
Assert::same('?int', Type::nullable('?int'));
14+
Assert::same('int', Type::nullable('?int', nullable: false));
15+
16+
Assert::same('null', Type::nullable('null'));
17+
Assert::same('NULL', Type::nullable('NULL'));
18+
Assert::exception(
19+
fn() => Type::nullable('null', nullable: false),
20+
Nette\InvalidArgumentException::class,
21+
'Type null cannot be not nullable.',
22+
);
23+
24+
Assert::same('mixed', Type::nullable('mixed'));
25+
Assert::exception(
26+
fn() => Type::nullable('mixed', nullable: false),
27+
Nette\InvalidArgumentException::class,
28+
'Type mixed cannot be not nullable.',
29+
);
30+
31+
Assert::same('int|float|string|null', Type::nullable('int|float|string'));
32+
Assert::same('int|float|string', Type::nullable('int|float|string', nullable: false));
33+
34+
Assert::same('NULL|int|float|string', Type::nullable('NULL|int|float|string'));
35+
Assert::same('int|float|string', Type::nullable('NULL|int|float|string', nullable: false));
1136

12-
Assert::same('?A', Type::nullable(A::class));
13-
Assert::same('?A', Type::nullable(A::class));
14-
Assert::same('A', Type::nullable(A::class, nullable: false));
37+
Assert::same('int|float|string|null', Type::nullable('int|float|string|null'));
38+
Assert::same('int|float|string', Type::nullable('int|float|string|null', nullable: false));
39+
40+
Assert::same('int|float|null|string', Type::nullable('int|float|null|string'));
41+
Assert::same('int|float|string', Type::nullable('int|float|null|string', nullable: false));
42+
43+
Assert::exception(
44+
fn() => Type::nullable('Foo&Bar'),
45+
Nette\InvalidArgumentException::class,
46+
'Intersection types cannot be nullable.',
47+
);
48+
Assert::same('Foo&Bar', Type::nullable('Foo&Bar', nullable: false));
49+
50+
51+
// Union
52+
Assert::same('A|string', Type::union(A::class, Type::String));
1553

16-
Assert::same('?A', Type::nullable('?A'));
17-
Assert::same('A', Type::nullable('?A', nullable: false));
54+
// Intersection
55+
Assert::same('A&string', Type::intersection(A::class, Type::String));

0 commit comments

Comments
 (0)