Narrowing Types
Narrowing a type is usually needed when we have a union type like bool|int
and we want to work only with one of the types in a branch. Other use case for narrowing is when we want to execute code only for a specific subtype, for example when we’re interested only in InvalidArgumentException
but we’ve got Exception
in a variable.
PHPStan supports several ways of narrowing types.
Strict comparison operators #
In case of scalar values type can be narrowed using ===
and !==
operators:
if ($stringOrBool === true) {
// $stringOrBool is true here
}
These operators can either be used in conditions, or in an assert()
call:
assert($stringOrBool === true);
// $intOrString is true after the assert() call
Type-checking functions #
is_array()
is_bool()
is_callable()
is_float()
/is_double()
/is_real()
is_int()
/is_integer()
/is_long()
is_numeric()
is_iterable()
is_null()
is_object()
is_resource()
is_scalar()
is_string()
is_subclass_of()
is_a
These functions can either be used in conditions, or in an assert()
call.
instanceof operator #
// $exception is Exception
if ($exception instanceof \InvalidArgumentException) {
// $exception is \InvalidArgumentException
}
The instanceof
operator can either be used in conditions, or in an assert()
call.
Custom type-checking functions and methods #
Narrowing types can also be performed by assertion libraries like webmozart/assert and beberlei/assert. You can install PHPStan extensions for these libraries so that PHPStan can understand how these custom function calls narrow down types.
Let’s consider a custom type-checking function like this:
public function foo(object $object): void
{
$this->checkType($object);
$object->doSomething(); // Call to an undefined method object::doSomething().
}
public function checkType(object $object): void
{
if (!$object instanceof \BarService) {
throw new WrongObjectTypeException();
}
}
During the analysis of the foo()
method, PHPStan doesn’t understand that the type of $object
was narrowed to BarService
because it doesn’t descend to called functions and symbols, it just reads their typehints and PHPDocs.
Recommended ways of solving this problem are:
- Switching to inlined type check - having
instanceof
right in thefoo()
method. - Switching to an assertion library like webmozart/assert or beberlei/assert and using it directly in the
foo()
method. - Writing a custom type-specifying extension for the
checkType()
method.