Utility types
Psalm supports some magical utility types that brings superpower to the PHP type system.
key-of<T>
(Psalm 5.0+)
The key-of
utility returns the offset-type for any array type.
Some examples:
key-of<Foo\Bar::ARRAY_CONST>
evaluates to offset-type ofARRAY_CONST
(Psalm 3.3+)key-of<list<mixed>>
evaluates toint
key-of<array{a: mixed, b: mixed}|array{c: mixed}>
evaluates to'a'|'b'|'c'
key-of<string[]>
evaluates toarray-key
key-of<T>
evaluates to the template param's offset-type (ensure@template T of array
)
Notes on template usage
If you use key-of
with a template param, you can fulfill the type check only with these allowed methods:
array_keys($t)
array_key_first($t)
array_key_last($t)
Currently array_key_exists($key, $t)
does not infer that $key
is of key-of<T>
.
/**
* @template T of array
* @param T $array
* @return list<key-of<T>>
*/
function getKeys($array) {
return array_keys($array);
}
value-of<T>
(Psalm 5.0+)
The value-of
utility returns the value-type for any array type.
Some examples:
value-of<Foo\Bar::ARRAY_CONST>
evaluates to value-type ofARRAY_CONST
(Psalm 3.3+)value-of<list<float>>
evaluates tofloat
value-of<array{a: bool, b: int}|array{c: string}>
evaluates tobool|int|string
value-of<string[]>
evaluates tostring
value-of<T>
evaluates to the template param's value-type (ensure@template T of array
)
Use with enumerations
In addition to array-types, value-of
can also be used to specify an int
or string
that contains one of the possible values of a BackedEnum
:
value-of<Suit>
evaluates to'H'|'D'|'C'|'S'
(see Backed enumerations)value-of<BinaryDigits>
evaluates to0|1
Notes on template usage
If you use value-of
with a template param, you can fulfill the type check only with these allowed methods:
array_values
/**
* @template T of array
* @param T $array
* @return value-of<T>[]
*/
function getValues($array) {
return array_values($array);
}
Currently in_array($value, $t)
does not infer that $value
is of value-of<T>
.
properties-of<T>
(Psalm 5.0+)
This collection of utility types construct a keyed-array type, with the names of non-static properties of a class as keys, and their respective types as values. This can be useful if you need to convert objects into arrays.
class A {
public string $foo = 'foo!';
public int $bar = 42;
/**
* @return properties-of<self>
*/
public function asArray(): array {
return [
'foo' => $this->foo,
'bar' => $this->bar,
];
}
/**
* @return list<key-of<properties-of<self>>>
*/
public function attributeNames(): array {
return ['foo', 'bar']
}
}
Variants
Note that properties-of<T>
will return all non-static properties. There are the following subtypes to pick only
properties with a certain visibility:
public-properties-of<T>
protected-properties-of<T>
private-properties-of<T>
Sealed array support
Use final classes if you want to properties-of and get_object_vars to return sealed arrays:
/**
* @template T
* @param T $object
* @return properties-of<T>
*/
function asArray($object): array {
return get_object_vars($object);
}
class A {
public string $foo = 'foo!';
public int $bar = 42;
}
final class B extends A {
public float $baz = 2.1;
}
$a = asArray(new A);
/** @psalm-trace $a */; // array{foo: string, bar: int, ...}
$b = asArray(new B);
/** @psalm-trace $b */; // array{foo: string, bar: int, baz: float}
class-string-map<T of Foo, T>
Used to indicate an array where each value is equal an instance of the class string contained in the key:
<?php
/**
* @psalm-consistent-constructor
*/
class Foo {}
/**
* @psalm-consistent-constructor
*/
class Bar extends Foo {}
class A {
/** @var class-string-map<T of Foo, T> */
private static array $map = [];
/**
* @template U of Foo
* @param class-string<U> $class
* @return U
*/
public static function get(string $class) : Foo {
if (isset(self::$map[$class])) {
return self::$map[$class];
}
self::$map[$class] = new $class();
return self::$map[$class];
}
}
$foo = A::get(Foo::class);
$bar = A::get(Bar::class);
/** @psalm-trace $foo */; // Foo
/** @psalm-trace $bar */; // Bar
If we had used an array<class-string<Foo>, Foo>
instead of a class-string-map<T of Foo, T>
in the above example, we would've gotten some false positive InvalidReturnStatement
issues, caused by the lack of a type assertion inside the isset
.
On the other hand, when using class-string-map
, Psalm assumes that the value obtained by using a key class-string<T>
is always equal to T
.
Unbounded templates can also be used for unrelated classes:
<?php
/**
* @psalm-consistent-constructor
*/
class Foo {}
/**
* @psalm-consistent-constructor
*/
class Bar {}
/**
* @psalm-consistent-constructor
*/
class Baz {}
class A {
/** @var class-string-map<T, T> */
private static array $map = [];
/**
* @template U
* @param class-string<U> $class
* @return U
*/
public static function get(string $class) : object {
if (isset(self::$map[$class])) {
return self::$map[$class];
}
self::$map[$class] = new $class();
return self::$map[$class];
}
}
$foo = A::get(Foo::class);
$bar = A::get(Bar::class);
$baz = A::get(Baz::class);
/** @psalm-trace $foo */; // Foo
/** @psalm-trace $bar */; // Bar
/** @psalm-trace $baz */; // Baz
T[K]
Used to get the value corresponding to the specified key:
<?php
/**
* @template T of array
* @template TKey of string
* @param T $arr
* @param TKey $k
* @return T[TKey]
*/
function a(array $arr, string $k): mixed {
assert(isset($arr[$k]));
return $arr[$k];
}
$a = a(['test' => 123], 'test');
/** @psalm-trace $a */; // 123
Type aliases
Psalm allows defining type aliases for complex types (like array shapes) which must be reused often:
/**
* @psalm-type PhoneType = array{phone: string}
*/
class Phone {
/**
* @psalm-return PhoneType
*/
public function toArray(): array {
return ["phone" => "Nokia"];
}
}
You can use the @psalm-import-type
annotation to import a type defined with @psalm-type
if it was defined somewhere else.
<?php
/**
* @psalm-import-type PhoneType from Phone
*/
class User {
/**
* @psalm-return PhoneType
*/
public function toArray(): array {
return array_merge([], (new Phone())->toArray());
}
}
You can also alias a type when you import it:
<?php
/**
* @psalm-import-type PhoneType from Phone as MyPhoneTypeAlias
*/
class User {
/**
* @psalm-return MyPhoneTypeAlias
*/
public function toArray(): array {
return array_merge([], (new Phone())->toArray());
}
}
Variable templates
Variable templates allow directly using variables instead of template types, for example instead of the following verbose example:
<?php
/**
* @template TA of string
* @template TB of string
* @template TChoose of bool
* @param TA $a
* @param TB $b
* @param TChoose $choose
* @return (TChoose is true ? TA : TB)
*/
function pick(string $a, string $b, bool $choose): string {
return $choose ? $a : $b;
}
$a = pick('a', 'b', true);
/** @psalm-trace $a */; // 'a'
$a = pick('a', 'b', false);
/** @psalm-trace $a */; // 'b'
We can instead use variable templates like so:
<?php
/**
* @return ($choose is true ? $a : $b)
*/
function pick(string $a, string $b, bool $choose): string {
return $choose ? $a : $b;
}
$a = pick('a', 'b', true);
/** @psalm-trace $a */; // 'a'
$a = pick('a', 'b', false);
/** @psalm-trace $a */; // 'b'