vimeo/psalm
Psalm is a powerful PHP static analysis tool that finds type errors and bugs before runtime. Install via Composer, configure for your codebase, and run it locally or try the live demo at psalm.dev. Docs and integrations available for teams and CI.
Installation:
composer require --dev vimeo/psalm
Initialize Psalm with default config:
vendor/bin/psalm --init
This generates a psalm.xml in your project root.
First Run:
vendor/bin/psalm
Analyze specific files:
vendor/bin/psalm src/Controller/UserController.php
Key Configurations:
psalm.xml to define:
<projectFiles>
<directory name="src" />
<directory name="app" />
</projectFiles>
<phpVersion>8.1</phpVersion>
First Use Case: Run Psalm in CI/CD pipelines to catch type errors early:
# .github/workflows/psalm.yml
jobs:
psalm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: composer install
- run: vendor/bin/psalm --no-cache
Pre-Commit Hook:
composer require --dev php-parallel-lint/php-parallel-lint
composer require --dev dealerdirect/phpcodesniffer-composer-installer
Add to composer.json:
"scripts": {
"test": [
"@phpcs",
"@psalm"
]
}
Create .php-cs-fixer.dist.php and .phpcs.xml for linting.
Incremental Analysis:
Use --diff for faster local runs:
vendor/bin/psalm --diff
Team Integration:
--shepherd to track type coverage in Shepherd.psalm.xml to ignore tests:
<ignoreFiles>
<directory name="tests" />
</ignoreFiles>
Custom Rules:
Extend Psalm with custom rules by creating a Ruleset class:
// src/Rules/NoHardcodedUrlsRule.php
namespace App\Rules;
use Psalm\Plugin\Ruleset\Ruleset;
class NoHardcodedUrlsRule extends Ruleset {
public function getRules(): array {
return [
NoHardcodedUrls::class,
];
}
}
Register in psalm.xml:
<pluginClass>App\Rules\NoHardcodedUrlsRule</pluginClass>
Fixing Issues:
Use psalter to auto-fix common issues:
vendor/bin/psalter --issues=MissingReturnType --dry-run
False Positives:
@psalm-suppress:
/** @psalm-suppress MixedReturnStatement */
function foo() {
return $this->bar(); // bar() returns mixed
}
@var annotations for dynamic types:
/** @var array<int, string> */
$dynamicArray = [];
Performance:
--no-cache to force a full analysis.--threads=8 for multi-core systems (adjust based on your machine).Configuration Quirks:
<strictness>1</strictness> for stricter checks.<platform> tags to define global constants:
<platform>
<constant name="APP_ENV" value="local" />
</platform>
vendor/ is excluded:
<excludeFiles>
<directory name="vendor" />
</excludeFiles>
Debugging:
--stats to see analysis details:
vendor/bin/psalm --stats
--log-level=3.Refactoring Risks:
--dry-run with psalm-refactor:
vendor/bin/psalm-refactor --move "App\Old\*" --into "App\New\" --dry-run
Plugin Limitations:
Psalm\Plugin\Manipulation\Manipulation for psalter.CI/CD Tips:
- uses: actions/cache@v3
with:
path: ~/.cache/psalm
key: ${{ runner.os }}-psalm-${{ hashFiles('**/composer.lock') }}
vendor/bin/psalm || exit 1
Type Coverage:
@psalm-assert for runtime assertions:
/** @psalm-assert string $var */
$var = $this->getString();
Legacy Code:
<strictness>0</strictness> initially.@psalm-ignore-all for entire files if needed (last resort).Edge Cases:
@property:
class DynamicClass {
/** @property string $dynamicProp */
}
@method for __call:
class Proxy {
/** @method string getData() */
}
How can I help you explore Laravel packages today?