Array types
In PHP, the array
type is commonly used to represent three different data structures:
List:
<?php
$a = [1, 2, 3, 4, 5];
<?php
$a = [0 => 'hello', 5 => 'goodbye'];
$b = ['a' => 'AA', 'b' => 'BB', 'c' => 'CC'];
Makeshift Structs:
<?php
$a = ['name' => 'Psalm', 'type' => 'tool'];
PHP treats all these arrays the same, essentially (though there are some optimisations under the hood for the first case).
Psalm has a few different ways to represent arrays in its type system:
Generic arrays
Psalm uses a syntax borrowed from Java that allows you to denote the types of both keys and values:
/** @return array<TKey, TValue> */
You can also specify that an array is non-empty with the special type non-empty-array<TKey, TValue>
.
PHPDoc syntax
PHPDoc allows you to specify the type of values a generic array holds with the annotation:
/** @return ValueType[] */
In Psalm this annotation is equivalent to @psalm-return array<array-key, ValueType>
.
Generic arrays encompass both associative arrays and lists.
Lists
(Psalm 3.6+)
Psalm supports a list
type that represents continuous, integer-indexed arrays like ["red", "yellow", "blue"]
.
A frequent way to create a list is with the $arr[] =
notation.
These arrays will return true to array_is_list($arr)
(PHP 8.1+) and represent a large percentage of all array usage in PHP applications.
A list
type is of the form list<SomeType>
, where SomeType
is any permitted union type supported by Psalm.
list
is a subtype ofarray<int, mixed>
list<Foo>
is a subtype ofarray<int, Foo>
.
List types show their value in a few ways:
<?php
/**
* @param array<int, string> $arr
*/
function takesArray(array $arr) : void {
if ($arr) {
// this index may not be set
echo $arr[0];
}
}
/**
* @psalm-param list<string> $arr
*/
function takesList(array $arr) : void {
if ($arr) {
// list indexes always start from zero,
// so a non-empty list will have an element here
echo $arr[0];
}
}
takesArray(["hello"]); // this is fine
takesArray([1 => "hello"]); // would trigger bug, without warning
takesList(["hello"]); // this is fine
takesList([1 => "hello"]); // triggers warning in Psalm
Array shapes
Psalm supports a special format for arrays where the key offsets are known: array shapes, also known as "object-like arrays".
Given an array
<?php
["hello", "world", "foo" => new stdClass, 28 => false];
Psalm will type it internally as:
array{0: string, 1: string, foo: stdClass, 28: false}
You can specify types in that format yourself, e.g.
/** @return array{foo: string, bar: int} */
Optional keys can be denoted by a trailing ?
, e.g.:
/** @return array{optional?: string, bar: int} */
You can use "one-line" comments (similar to PHP), e.g:
/** @return array { // Array with comments.
* // Comments can be placed on their own line.
* foo: string, // An array key description.
* bar: array {, // Another array key description.
* 'foo//bar': string, // Array key with "//" in it's name.
* },
* }
*/
Tip: if you find yourself copying the same complex array shape over and over again to avoid InvalidArgument
issues, try using type aliases, instead.
Validating array shapes
Use Valinor in strict mode to easily assert array shapes at runtime using Psalm array shape syntax (instead of manually asserting keys with isset):
try {
$array = (new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(
'array{a: string, b: int}',
json_decode(file_get_contents('https://.../'), true)
);
/** @psalm-trace $array */; // array{a: string, b: int}
echo $array['a'];
echo $array['b'];
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
// Do something…
}
Valinor provides both runtime and static Psalm assertions with full Psalm syntax support and many other features, check out the Valinor documentation for more info!
List shapes
Starting in Psalm 5, Psalm also supports a special format for list arrays where the key offsets are known.
Given a list array
<?php
["hello", "world", new stdClass, false];
Psalm will type it internally as:
list{string, string, stdClass, false}
You can specify types in that format yourself, e.g.
/** @return list{string, int} */
/** @return list{0: string, 1: int} */
Optional keys can be denoted by a specifying keys for all elements and specifying a trailing ?
for optional keys, e.g.:
/** @return list{0: string, 1?: int} */
List shapes are essentially n-tuples from a type theory perspective.
Unsealed array and list shapes
Starting from Psalm v5, array shapes and list shapes can be marked as open by adding ...
as their last element.
Here we have a function handleOptions
that takes an array of options. The type tells us it has a single known key with type string
, and potentially many other keys of unknown types.
/** @param array{verbose: string, ...} $options */
function handleOptions(array $options): float {
if ($options['verbose']) {
var_dump($options);
}
}
$options = get_opt(/* some code */);
$options['verbose'] = isset($options['verbose']);
handleOptions($options);
...
is a shorthand for ...<array-key, mixed>
you can use other array generic types to provide more information about the open shape.
// This is an open array
/** @param array{someKey: string, ...} */
// Which is the same as
/** @param array{someKey: string, ...<array-key, mixed>} */
// But it can be further locked down with a shape ...<TKey, TValue>
/** @return array{someKey: string, ...<int, bool>} */
Callable arrays
An array holding a callable, like PHP's native call_user_func()
and friends supports it:
<?php
$callable = ['myClass', 'aMethod'];
$callable = [$object, 'aMethod'];
non-empty-array
An array which is not allowed to be empty.
Generic syntax is also supported: non-empty-array<string, int>
.