← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Psalm Type Annotations

style PHP 7.1+ Intermediate
debt(d3/e3/b3/t5)
d3 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'default linter catches the common case' (d3), since Psalm itself (listed in detection_hints.tools) flags missing/imprecise annotations when run, and it's a standard part of PHP static-analysis tooling.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3), as quick_fix shows adding annotations like @psalm-return array{...} or @psalm-immutable is a docblock-level change per class/function, not a one-liner but a small pattern replacement.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3), since annotations apply across web/cli/queue contexts but are docblock metadata — they don't reshape architecture, though maintaining annotation accuracy is an ongoing tax.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5), grounded in the misconception that Psalm and PHPStan annotations are fully interchangeable — they overlap heavily but @psalm-* prefixes and generics support differ, a documented gotcha devs eventually learn.

About DEBT scoring →

Also Known As

Psalm PHP Psalm annotations @psalm-param @template Psalm

TL;DR

Psalm's docblock annotations — @param, @return, @template, @psalm-type — extend PHP's native type system to express generics, aliases, and complex types.

Explanation

Psalm's annotation system extends PHP's type declarations with expressive docblock tags. @param and @return accept Psalm's extended type syntax: non-empty-string, positive-int, list<User>, array{id: int, name: string}. @template T enables generic class and function definitions: @template T @param array<T> $items @return T|null. @psalm-type creates reusable type aliases: @psalm-type UserId = positive-int. @psalm-suppress suppresses specific issues with a required code. @psalm-assert (and @psalm-assert-if-true) teach Psalm about custom assertion functions. @readonly (pre-8.1) marks properties immutable. Psalm's @var inline annotations narrow types within a method body when inference falls short. All annotations are backward-compatible — they live in docblocks and don't affect runtime.

Common Misconception

Psalm and PHPStan annotations are fully interchangeable. Psalm uses @psalm-* prefixed annotations and supports generics via @template more extensively than PHPStan. Many annotations overlap, but each tool has unique capabilities — projects using both need to reconcile their annotation sets.

Why It Matters

Psalm's advanced annotations (@psalm-template, @psalm-param, @psalm-return) describe generics and complex types that PHP's native type system cannot express — enabling deep static analysis of collection types and callbacks.

Common Mistakes

  • Not using @psalm-template for generic collection classes — Psalm cannot infer the element type without it.
  • Using @return array when @return array<string, User> gives Psalm precise type information.
  • Not running Psalm in CI — annotations are useless if violations are never surfaced.
  • Suppressing Psalm errors with @psalm-suppress without understanding the underlying type issue.

Code Examples

✗ Vulnerable
// Without Psalm annotations — type information lost:
/** @return array */
public function findAll(): array { /* returns User[] */ }

$users = $repo->findAll();
$users[0]->email; // Psalm: cannot determine type of $users[0]

// With annotation:
/** @return array<int, User> */
public function findAll(): array { /* returns User[] */ }
$users[0]->email; // Psalm: knows this is User — validates property access
✓ Fixed
// Psalm type annotations — extend PHP's type system

/** @param non-empty-string \$email */
function sendEmail(string \$email): void {}

/** @return list<User> */  // ordered array, integer keys 0..n
function getUsers(): array {}

/** @template T */
function first(array \$items): mixed {}

/** @psalm-immutable */
class Money {
    public function __construct(
        public readonly int \$amount,
        public readonly string \$currency,
    ) {}
}

// Taint analysis — track user input through the codebase:
/** @psalm-taint-source input */
function getUserInput(): string { return \$_POST['data'] ?? ''; }

/** @psalm-taint-sink sql */
function runQuery(string \$sql): void {}

// psalm will warn if tainted input flows to a SQL sink without sanitisation

Added 15 Mar 2026
Edited 22 Mar 2026
Views 136
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 6 pings S 3 pings S 6 pings M 4 pings T 2 pings W 4 pings T 3 pings F 3 pings S 5 pings S 4 pings M 0 pings T 0 pings W 2 pings T
Google 1 ChatGPT 1
No pings yesterday
ChatGPT 83 Perplexity 10 Amazonbot 6 Scrapy 6 Google 5 Ahrefs 3 SEMrush 3 Unknown AI 2 Meta AI 1 Bing 1
crawler 116 crawler_json 4
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Medium
⚡ Quick Fix
Add @psalm-immutable, @psalm-pure, and @psalm-return array{id:int,name:string} to express things PHPStan's basic types can't — Psalm and PHPStan share most annotations so most work for both
📦 Applies To
PHP 7.1+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
@return array with no shape; missing @psalm-immutable on value objects; no @psalm-pure on pure functions allowing Psalm to optimise analysis
Auto-detectable: ✓ Yes psalm phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Low ✓ Auto-fixable Fix: Low Context: File

✓ schema.org compliant