4.3 KiB
Adding assertions
Psalm has four docblock annotations that allow you to specify that a function verifies facts about variables and properties:
@psalm-assert
(used when throwing an exception)@psalm-assert-if-true
/@psalm-assert-if-false
(used when returning abool
)@psalm-if-this-is
(used when calling a method)
A list of acceptable assertions can be found here.
Examples
If you have a class that verified its input is an array of strings, you can make that clear to Psalm:
<?php
/** @psalm-assert string[] $arr */
function validateStringArray(array $arr) : void {
foreach ($arr as $s) {
if (!is_string($s)) {
throw new UnexpectedValueException('Invalid value ' . gettype($s));
}
}
}
This enables you to call the validateStringArray
function on some data and have Psalm understand that the given data must be an array of strings:
<?php
function takesString(string $s) : void {}
function takesInt(int $s) : void {}
function takesArray(array $arr) : void {
takesInt($arr[0]); // this is fine
validateStringArray($arr);
takesInt($arr[0]); // this is an error
foreach ($arr as $a) {
takesString($a); // this is fine
}
}
Similarly, @psalm-assert-if-true
and @psalm-assert-if-false
will filter input if the function/method returns true
and false
respectively:
<?php
class A {
public function isValid() : bool {
return (bool) rand(0, 1);
}
}
class B extends A {
public function bar() : void {}
}
/**
* @psalm-assert-if-true B $a
*/
function isValidB(A $a) : bool {
return $a instanceof B && $a->isValid();
}
/**
* @psalm-assert-if-false B $a
*/
function isInvalidB(A $a) : bool {
return !$a instanceof B || !$a->isValid();
}
function takesA(A $a) : void {
if (isValidB($a)) {
$a->bar();
}
if (isInvalidB($a)) {
// do something
} else {
$a->bar();
}
$a->bar(); //error
}
As well as getting Psalm to understand that the given data must be a certain type, you can also show that a variable must be not null:
<?php
/**
* @psalm-assert !null $value
*/
function assertNotNull($value): void {
// Some check that will mean the method will only complete if $value is not null.
}
And you can check on null values:
<?php
/**
* @psalm-assert-if-true null $value
*/
function isNull($value): bool {
return ($value === null);
}
Asserting return values of methods
You can also use the @psalm-assert-if-true
and @psalm-assert-if-false
annotations to assert return values of
methods inside classes. As you can see, Psalm even allows you to specify multiple annotations in the same DocBlock:
<?php
class Result {
/**
* @var ?Exception
*/
private $exception;
/**
* @psalm-assert-if-true Exception $this->exception
* @psalm-assert-if-true Exception $this->getException()
*/
public function hasException(): bool {
return $this->exception !== null;
}
public function getException(): ?Exception {
return $this->exception;
}
public function foo(): void {
if( $this->hasException() ) {
// Psalm now knows that $this->exception is an instance of Exception
echo $this->exception->getMessage();
}
}
}
$result = new Result;
if( $result->hasException() ) {
// Psalm now knows that $result->getException() will return an instance of Exception
echo $result->getException()->getMessage();
}
Please note that the example above only works if you enable method call memoization in the config file or annotate the class as immutable.
You can also make sure, when calling a method, that its object has some specific template arguments:
<?php
/**
* @template T
*/
class a {
/**
* @var T
*/
private $data;
/**
* @param T $data
*/
public function __construct($data) {
$this->data = $data;
}
/**
* @psalm-if-this-is a<int>
*/
public function test(): void {
}
}
$i = new a(123);
$i->test();
$i = new a("test");
// IfThisIsMismatch - Class is not a<int> as required by psalm-if-this-is
$i->test();