Psalm Documentation

repository·6.x·Indexed 27 days ago

https://github.com/vimeo/psalm

Psalm is a static analysis tool for PHP applications designed to identify errors, find type-related bugs, and improve code quality without executing the code. It features taint analysis for security vulnerabilities, mixed type warnings, property initialization checks, and a Language Server for IDE compatibility. The tool supports a wide range of PHPDoc annotations for type assertions (@psalm-assert), template updates (@psalm-this-out), and issue suppression (@psalm-suppress).

Tokens
97.1K
Snippets
373
Records
558
Agent score
91%

What's inside Psalm

  1. Overview of Psalm features

    6.x

    Psalm is a static analysis tool for PHP designed to find type-related bugs. Key features include:

    • Mixed type warnings: Identifies mixed placeholder types that may mask bugs.
    • Intelligent logic checks: Detects redundant or impossible logical assertions (e.g., if ($a && !$a) or if ($a) {} elseif ($a) {}).
    • Property initialisation checks: Ensures all object properties are assigned values after the constructor completes.
    • Taint analysis: Detects security vulnerabilities in your code.
    • Language Server: Provides IDE compatibility via a Language Server protocol.
    • Automatic fixes: Automatically resolves many identified issues.
    • Automatic refactoring: Performs simple refactors via the command line.
  2. Specify string or int literal options (enums)

    6.x

    You can restrict a parameter to a specific set of allowed string or integer values using union types of literals. This is useful for ensuring all paths in a switch or conditional block are covered.

    Using Literals

    /**
     * @param 'a'|'b' $s
     */
    function foo(string $s) : string { ... }

    Using Class Constants

    You can specify allowed values using class constants:

    /**
     * @param A::FOO | A::BAR $s
     */
    function foo(string $s) : string { ... }

    Using Wildcards for Constants

    If class constants share a common prefix, you can use a wildcard to include all of them:

    /**
     * @param A::STATUS_* $s
     */
    function foo(string $s) : string { ... }
    /**
     * @param A::STATUS_* $s
     */
    function foo(string $s) : string {
      switch ($s) {
        case A::STATUS_FOO:
          return 'hello';
    
        default:
          // any other status
          return 'goodbye';
      }
    }
  3. Understand and mitigate the TaintedFile issue

    6.x

    The TaintedFile rule is emitted by Psalm when user-controlled input (tainted data) is passed into sensitive file operations. This can lead to various security vulnerabilities depending on the operation used:

    • Creating/Modifying files (e.g., file_put_contents): Risk of Remote Code Execution (RCE) if files are written to the web root or existing PHP files are modified.
    • Reading files (e.g., file_get_contents): Risk of exposing sensitive filesystem data like configuration values or source code.
    • Deleting files (e.g., unlink): Risk of Denial of Service (DoS) or RCE by deleting application code or critical configuration files like .htaccess.

    Mitigations

    To resolve this issue, implement one of the following:

    1. Allowlist approach: Verify filenames against a predefined list of permitted names before performing the operation.
    2. Sanitization: Strip dangerous characters such as .., \, and / from user-controlled filenames to prevent directory traversal.
    <?php
    
    // Example of code that triggers the TaintedFile issue:
    $content = file_get_contents($_GET['header']);
    echo $content;
  4. Use the #[Override] attribute for overridden methods

    6.x

    To satisfy the ensureOverrideAttribute check and make method intentions explicit, declare the #[\Override] attribute on methods that override a parent class or interface method.

    Note: The #[\Override] attribute is compatible with all PHP versions, including PHP 4. However, if you are using PHP 8.0 through 8.2, you must require symfony/polyfill-php83 to provide the missing attribute support.

    <?php
    
    class A {
        function receive(): void
        {
        }
    }
    
    class B extends A {
        #[\Override]
        function receive(): void
        {
        }
    }
  5. Configure Psalm error levels

    6.x

    Psalm supports eight levels of strictness, ranging from level 1 (most strict) to level 8 (most lenient).

    • Level 1: The strictest mode. All detectable issues are treated as errors, including situations where Psalm cannot infer a type (e.g., Mixed* issues).
    • Level 2: The default level if none is specified. It ignores Mixed* issues but treats most other issues as errors.
    • Level 3: More lenient; allows missing parameter types, return types, and property types.
    • Level 4: Ignores issues regarding possible problems (potential false positives where code behavior might be guaranteed but not inferable by Psalm).
    • Levels 5-8: Increasingly permissive, allowing for more non-verifiable code.
  6. Restrict parameters to class strings using class-string<T> or T::class

    6.x

    When annotating a parameter that should accept a class string, you have two options depending on whether you want to allow subclasses:

    1. Allow subclasses: Use @param class-string<Foo> $param. This accepts the class Foo or any class that extends Foo.
    2. Exact class only: Use @param Foo::class $param. This accepts only the exact class Foo and will fail if a subclass is provided.
    <?php
    class A {}
    class AChild extends A {}
    class B {}
    class BChild extends B {}
    
    /**
     * Accepts A, B, or any of their subclasses
     * @param class-string<A>|class-string<B> $s
     */
    function foo(string $s) : void {}
    
    /**
     * Accepts ONLY the exact classes A or B
     * @param A::class|B::class $s
     */
    function bar(string $s) : void {}
    
    foo(A::class); // works
    foo(AChild::class); // works
    foo(B::class); // works
    foo(BChild::class); // works
    
    bar(A::class); // works
    bar(AChild::class); // fails
    bar(B::class); // works
    bar(BChild::class); // fails
  7. Upgrade from Psalm 5 to Psalm 6

    6.x

    When upgrading to Psalm 6, note the following breaking changes and requirements:

    Requirements

    • The minimum PHP version is now PHP 8.1.17.

    Configuration Changes

    • Config::$shepherd_host has been replaced by Config::$shepherd_endpoint.
    • Config::$find_unused_code now defaults to true.
    • Config::$find_unused_baseline_entry now defaults to true.
    • The configuration settings ignoreInternalFunctionFalseReturn and ignoreInternalFunctionNullReturn now default to false.

    Internal API & Type Changes (Breaking Changes)

    • List Types: TList, TNonEmptyList, and TCallableList classes have been removed. Use \Psalm\Type::getListAtomic(), \Psalm\Type::getNonEmptyListAtomic(), or \Psalm\Type::getCallableListAtomic() to instantiate list atomics. Alternatively, instantiate TKeyedArray objects with is_list=true.
    • TKeyedArray: The optional boolean parameter of TKeyedArray::getGenericArrayType was removed and replaced with a string parameter.
    • TCallable Types: TCallableArray and TCallableList were replaced by TCallableKeyedArray.
    • Taint Analysis: The value of Psalm\Type\TaintKindGroup::ALL_INPUT has changed due to new TaintKind values (INPUT_EXTRACT, INPUT_SLEEP, INPUT_XPATH). Default values for $taint parameters in Psalm\Codebase::addTaintSource() and Psalm\Codebase::addTaintSink() have also changed.
    • Codebase Methods: Codebase::getSymbolLocation() and Codebase::getSymbolInformation() are replaced by Codebase::getSymbolLocationByReference().
    • Function Storage: FunctionLikeStorage::getSignature() is replaced by FunctionLikeStorage::getCompletionSignature(). The property $unused_docblock_params is now $unused_docblock_parameters.
    • Plugin API: Psalm\Plugin\ArgTypeInferer::infer now returns Union|null instead of Union|false.
  8. Migrate Psalm 5 to Psalm 6: Immutable Types and Union Mutations

    6.x

    In Psalm 6, all atomic types, Psalm\Type\Union, Psalm\CodeLocation, and storages are now fully immutable. To modify these, you must use new setter methods which return a new instance instead of altering the original.

    Modifying Psalm\Type\Union

    • Recommended: Use the new setter methods on Psalm\Type\Union. These return a new instance.
    • Batch Updates: If performing many consecutive property sets, use Psalm\Type\Union::setProperties to avoid excessive object creation.
    • Mutable Workflow: For complex mutations across multiple methods, use Psalm\Type\Union::getBuilder() to convert the union into a Psalm\Type\MutableUnion. Once finished, call Psalm\Type\MutableUnion::freeze() to return to a Psalm\Type\Union.

    Removed Methods

    The following methods have been moved from Psalm\Type\Union to Psalm\Type\MutableUnion:

    • replaceTypes
    • addType
    • removeType
    • substitute
    • replaceClassLike
  9. Resolve PossiblyFalseOperand using a ternary operator

    6.x

    Alternatively, you can suppress the PossiblyFalseOperand issue by using a ternary operator or the Elvis operator (?:) to provide a fallback value (like an empty string) if the function returns false.

    <?php
    
    function echoCommaPosition(string $str) : void {
        echo 'The comma is located at ' . (strpos($str, ',') ?: ''); 
    }
  10. Install Psalm via Composer

    6.x

    To install Psalm as a development dependency in your project, ensure you have PHP >= 8.2 and Composer installed, then run the following command. After installation, you can initialize a configuration file which will scan your project to determine an appropriate error level.

    composer require --dev vimeo/psalm
    ./vendor/bin/psalm --init
    ./vendor/bin/psalm --no-cache