2019-06-19 20:19:14 +02:00
# Adding assertions
2021-10-28 10:44:37 +02:00
Psalm has five docblock annotations that allow you to specify that a function verifies facts about variables and properties:
2019-06-19 20:19:14 +02:00
- `@psalm-assert` (used when throwing an exception)
- `@psalm-assert-if-true` /`@psalm-assert-if-false` (used when returning a `bool` )
2021-10-28 10:44:37 +02:00
- `@psalm-if-this-is` /`@psalm-this-out` (used when calling a method)
2019-06-19 20:19:14 +02:00
A list of acceptable assertions [can be found here ](assertion_syntax.md ).
## Examples
If you have a class that verified its input is an array of strings, you can make that clear to Psalm:
```php
2020-03-21 14:24:41 +01:00
< ?php
2019-06-19 20:19:14 +02:00
/** @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
2020-03-21 14:24:41 +01:00
< ?php
2019-06-19 20:19:14 +02:00
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
2020-03-21 14:24:41 +01:00
< ?php
2019-06-19 20:19:14 +02:00
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 {
2021-04-22 03:32:57 +02:00
return !$a instanceof B || !$a->isValid();
2019-06-19 20:19:14 +02:00
}
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
2020-03-21 14:24:41 +01:00
< ?php
2019-06-19 20:19:14 +02:00
/**
* @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
2020-03-21 14:24:41 +01:00
< ?php
2019-06-19 20:19:14 +02:00
/**
* @psalm -assert-if-true null $value
*/
function isNull($value): bool {
return ($value === null);
}
```
2021-07-04 21:34:22 +02:00
### 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
< ?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 ](https://psalm.dev/docs/running_psalm/configuration/#memoizemethodcallresults )
in the config file or annotate the class as [immutable ](https://psalm.dev/docs/annotating_code/supported_annotations/#psalm-immutable ).
2021-09-21 17:35:44 +02:00
2021-10-28 10:44:37 +02:00
You can use `@psalm-this-out` to change the template arguments of a method after a method call, to reflect changes to the object's internal state.
You can also make assertions on the object's template arguments using `@psalm-if-this-is` .
2021-09-21 17:35:44 +02:00
```php
< ?php
/**
* @template T
*/
class a {
/**
2021-10-28 10:44:37 +02:00
* @var list< T >
2021-09-21 17:35:44 +02:00
*/
2021-10-28 10:44:37 +02:00
private array $data;
2021-09-21 17:35:44 +02:00
/**
* @param T $data
*/
public function __construct($data) {
2021-10-28 10:44:37 +02:00
$this->data = [$data];
}
/**
* @template NewT
*
* @param NewT $data
*
* @psalm -this-out self< T | NewT >
*
* @return void
*/
public function addData($data) {
/** @var self< T | NewT > $this */
$this->data []= $data;
}
/**
* @template NewT
*
* @param NewT $data
*
* @psalm -this-out self< NewT >
*
* @return void
*/
public function setData($data) {
/** @var self< NewT > $this */
$this->data = [$data];
2021-09-21 17:35:44 +02:00
}
/**
* @psalm -if-this-is a< int >
*/
public function test(): void {
}
}
$i = new a(123);
2021-10-28 10:44:37 +02:00
// OK - $i is a< 123 >
$i->test();
$i->addData(321);
// OK - $i is a< 123 | 321 >
2021-09-21 17:35:44 +02:00
$i->test();
2021-10-28 10:44:37 +02:00
$i->setData("test");
2021-09-21 17:35:44 +02:00
// IfThisIsMismatch - Class is not a< int > as required by psalm-if-this-is
$i->test();
```