Typhoon Type is an object abstraction over the modern PHP type system. Use this library to build tools that work with sophisticated types.
Here are some examples of potential use-cases:
use Typhoon\Type\types;
$data = (new MyAwesomeJsonDecoder())->decode(
json: '[1, 0.5, "213"]',
type: types::list(types::numeric),
);
var_dump($data);
array(3) {
[0] => int(1)
[1] => float(0.5)
[2] => string(3) "213"
}
Or:
use Typhoon\Type\types;
final readonly class GetUserResponse
{
/**
* @param non-empty-string $name
* @param 'user'|'admin' $group
*/
public function __construct(
public Uuid $id,
public string $name,
public string $group,
) {}
}
echo (new MyAwesomeOpenApiGenerator())->generateSchema(types::object(GetUserResponse::class));
GetUserResponse:
type: object
properties:
id:
type: string
format: uuid
example: b609e6a9-bba6-4599-9faa-cc9977353bb4
name:
type: string
example: Hello world!
group:
type: string
enum: [ user, admin ]
composer require typhoon/type
Typhoon types can be constructed via the Typhoon\Type\types
static factory. Let's express this monstrous type via
the Typhoon DSL:
array{
a: non-empty-string,
b?: int|float,
c: Traversable<numeric-string, false>,
d: callable(PDO::*, TSend#Generator=, scalar...): void,
...
}
use Typhoon\Type\types;
$type = types::unsealedArrayShape([
'a' => types::nonEmptyString,
'b' => types::optional(types::union(types::int, types::float)),
'c' => types::object(Traversable::class, [types::numericString, types::false]),
'd' => types::callable(
parameters: [
types::classConstantMask(PDO::class),
types::param(types::classTemplate(Generator::class, 'TSend'), hasDefault: true),
types::param(types::scalar, variadic: true),
],
return: types::void,
),
]);
As you can see, creating types in Typhoon is a lot of fun, especially if you work in IDE with autocompletion 😉
Unlike other solutions, Typhoon Type does not expose concrete type classes in its API. Instead, it provides only
a common Type
interface, a type factory types
, and
a TypeVisitor
with destructurization.
This approach gives several advantages:
- The visitor has only a minimal subset of type methods that must be implemented when describing a type algebra. Complexity of the other types is hidden and can be completely ignored.
- Memory efficient enums can be used for all atomic types and for aliases of commonly used compound types.
- Using of downcasting via the
instanceof
operator is automatically discouraged, since allType
implementations are@internal
( see PHPStan: Why Is instanceof *Type Wrong and Getting Deprecated?).
To cast any type to string, use the Typhoon\Type\stringify()
function:
use Typhoon\Type\types;
use function Typhoon\Type\stringify;
var_dump(
stringify(
types::Generator(
key: types::nonNegativeInt,
value: types::classTemplate(Foo::class, 'T'),
send: types::scalar,
),
),
); // Generator<int<0, max>, T#Foo, scalar, mixed>
Typhoon team is currently working on a type comparator. Until it is released, you can use DefaultTypeVisitor for simple checks:
use Typhoon\Type\Type;
use Typhoon\Type\types;
use Typhoon\Type\Visitor\DefaultTypeVisitor;
/**
* @extends DefaultTypeVisitor<bool>
*/
final class BasicIntChecker extends DefaultTypeVisitor
{
public function int(Type $type, Type $minType, Type $maxType): mixed
{
return true;
}
public function intValue(Type $type, int $value): mixed
{
return true;
}
public function intMask(Type $type, Type $ofType): bool
{
return true;
}
protected function default(Type $type): bool
{
return false;
}
}
var_dump(types::positiveInt->accept(new BasicIntChecker())); // true
var_dump(types::callableString()->accept(new BasicIntChecker())); // false
PHPStan | Psalm | Typhoon |
---|---|---|
null |
null |
types::null , types::value(null) |
void |
void |
types::void |
never |
never |
types::never |
true |
true |
types::true , types::value(true) |
false |
false |
types::false , types::value(false) |
bool , boolean |
bool |
types::bool |
int , integer |
int |
types::int |
float , double |
float |
types::float |
string |
string |
types::string |
resource |
resource |
types::resource |
array |
array |
types::array |
iterable |
iterable |
types::iterable |
object |
object |
types::object |
Foo |
Foo |
types::object(Foo::class) |
Closure |
Closure |
types::Closure (an alias for types::object(Closure::class) ) |
Generator |
Generator |
types::Generator() (an alias for types::object(Generator::class) ) |
self |
self |
types::self() |
parent |
parent |
types::parent() |
static |
static |
types::static() |
callable |
callable |
types::callable |
?string |
?string |
types::nullable(types::string) |
int|string |
int|string |
types::union(types::int, types::string) |
Countable&Traversable |
Countable&Traversable |
types::intersection(types::object(Countable::class), types::object(Traversable::class)) |
mixed |
mixed |
types::mixed |
PHPStan | Psalm | Typhoon |
---|---|---|
literal-int |
literal-int |
types::literalInt |
123 |
123 |
types::int(123) , types::value(123) |
positive-int |
positive-int |
types::positiveInt |
negative-int |
negative-int |
types::negativeInt |
non-positive-int |
non-positive-int |
types::nonPositiveInt |
non-negative-int |
non-negative-int |
types::nonNegativeInt |
non-zero-int |
negative-int|positive-int |
types::nonZeroInt |
int<-5, 6> |
int<-5, 6> |
types::intRange(-5, 6) |
int<min, 6> |
int<min, 6> |
types::intRange(max: 6) |
int<-5, max> |
int<-5, max> |
types::intRange(min: -5) |
int-mask<1, 2, 4> |
int-mask<1, 2, 4> |
types::intMask(1, 2, 4) |
int-mask-of<Foo::INT_*> |
int-mask-of<Foo::INT_*> |
types::intMaskOf(types::classConstantMask(Foo::class, 'INT_') |
❌ | ❌ | types::literalFloat |
❌ | ❌ | types::floatRange(-0.001, 2.344) |
12.5 |
12.5 |
types::float(12.5) , types::value(12.5) |
numeric |
numeric |
types::numeric |
PHPStan | Psalm | Typhoon |
---|---|---|
non-empty-string |
non-empty-string |
types::nonEmptyString |
literal-string |
literal-string |
types::literalString |
'abc' |
'abc' |
types::string('abc') , types::value('abc') |
truthy-string , non-falsy-string |
truthy-string , non-falsy-string |
types::truthyString , types::nonFalsyString |
numeric-string |
numeric-string |
types::numericString |
callable-string |
callable-string |
types::callableString() |
class-string<Foo> |
class-string<Foo> |
types::classString(types::object(Foo::class)) |
Foo::class |
Foo::class |
types::class(Foo::class) |
class-string |
class-string |
types::classString |
❌ | interface-string |
❌ |
❌ | trait-string |
❌ |
❌ | enum-string |
❌ |
❌ | lowercase-string |
❌ |
PHPStan | Psalm | Typhoon |
---|---|---|
PHP_INT_MAX |
PHP_INT_MAX |
types::constant('PHP_INT_MAX') |
Foo::BAR |
Foo::BAR |
types::classConstant(Foo::class, 'BAR') |
Foo::IS_* |
Foo::IS_* |
types::classConstant(Foo::class, 'IS_*') , types::classConstantMask(Foo::class, 'IS_') |
PHPStan | Psalm | Typhoon |
---|---|---|
array-key |
array-key |
types::arrayKey |
Foo[] |
Foo[] |
types::array(value: types::object(Foo::class)) |
list<string> |
list<string> |
types::list(types::string) |
non-empty-list<string> |
non-empty-list<string> |
types::nonEmptyList(types::string) |
list{int, string} |
list{int, string} |
types::listShape([types::int, types::string]) |
list{int, 1?: string} |
list{int, 1?: string} |
types::listShape([types::int, types::optional(types::string)]) |
list{int, ...} |
list{int, ...} |
types::unsealedListShape([types::int]) |
❌ (issue) | list{int, ...<string>} |
types::unsealedListShape([types::int], types::string) |
array<string> |
array<string> |
types::array(value: types::string) |
array<int, string> |
array<int, string> |
types::array(types::int, types::string) |
non-empty-array<array-key, string> |
non-empty-array<array-key, string> |
types::nonEmptyArray(types::arrayKey, types::string) |
array{} |
array{} |
types::array() |
array{int, string} |
array{int, string} |
types::arrayShape([types::int, types::string]) |
array{int, a?: string} |
array{int, a?: string} |
types::arrayShape([types::int, 'a' => types::optional(types::string)]) |
array{int, ...} |
array{int, ...} |
types::unsealedArrayShape([types::int]) |
❌ (issue) | array{float, ...<int, string>} |
types::unsealedArrayShape([types::float], types::int, types::string) |
key-of<Foo::ARRAY> |
key-of<Foo::ARRAY> |
types::keyOf(types::classConstant(Foo::class, 'ARRAY')) |
value-of<Foo::ARRAY> |
value-of<Foo::ARRAY> |
types::valueOf(types::classConstant(Foo::class, 'ARRAY')) |
TArray[TKey] |
TArray[TKey] |
types::offset($arrayType, $keyType) |
iterable<object, string> |
iterable<object, string> |
types::iterable(types::object, types::string) |
iterable<string> |
iterable<string> |
types::iterable(value: types::string) |
Generator<TKey, TValue, TSend, TReturn> |
Generator<TKey, TValue, TSend, TReturn> |
types::object(Generator::class, [$key, $value, $send, $return]) , types::Generator($key, $value, $send, $return) |
callable&array |
callable-array |
types::callableArray() |
PHPStan | Psalm | Typhoon |
---|---|---|
Foo<string, float> |
Foo<string, float> |
types::object(Foo::class, [types::string, types::float]) |
self<string, float> |
self<string, float> |
types::self([types::string, types::float]) |
parent<string, float> |
parent<string, float> |
types::parent([types::string, types::float]) |
static<string, float> |
static<string, float> |
types::static([types::string, types::float]) |
object{prop: string} |
object{prop: string} |
types::object(['prop' => types::string]) |
object{prop?: string} |
object{prop?: string} |
types::object(['prop' => types::optional(types::string)]) |
PHPStan | Psalm | Typhoon |
---|---|---|
callable-string |
callable-string |
types::callableString() |
callable&array |
callable-array |
types::callableArray() |
callable(string): void |
callable(string): void |
types::callable([types::string], types::void) |
callable(string=): mixed |
callable(string=): mixed |
types::callable([types::param(types::string, hasDefault: true)]) |
callable(...string): mixed |
callable(...string): mixed |
types::callable([types::param(types::string, variadic: true)]) |
callable(&string): mixed |
callable(&string): mixed |
types::callable([types::param(types::string, byReference: true)]) |
Closure(string): void |
Closure(string): void |
types::Closure([types::string], types::void) |
Closure(string=): mixed |
Closure(string=): mixed |
types::Closure([types::param(types::string, hasDefault: true)]) |
Closure(...string): mixed |
Closure(...string): mixed |
types::Closure([types::param(types::string, variadic: true)]) |
Closure(&string): mixed |
Closure(&string): mixed |
types::Closure([types::param(types::string, byReference: true)]) |
pure-callable |
pure-callable |
❌ |
PHPStan | Psalm | Typhoon |
---|---|---|
scalar |
scalar |
types::scalar |
Template T |
Template T |
types::functionTemplate('foo', 'T') , types::classTemplate(Foo::class, 'T') , types::methodTemplate(Foo::class, 'bar', 'T') |
Alias X |
Alias X |
types::classAlias(Foo::class, 'X') |
(T is string ? true : false) |
(T is string ? true : false) |
types::conditional($type, types::string, types::true, types::false) |
($return is true ? string : void) |
($return is true ? string : void) |
types::conditional(types::functionArg('var_export', 'return'), types::true, types::string, types::void) |
!null (only in assertions) |
!null (only in assertions) |
types::not(types::null) |
❌ | properties-of<T> |
❌ |
❌ | class-string-map<T of Foo, T> |
❌ |
open-resource |
open-resource |
❌ |
closed-resource |
closed-resource |
❌ |