php-standard-library/psalm-plugin
Psalm plugin for PHP Standard Library (PSL) that improves type inference for PSL Type specifications (e.g., shape/optional), producing more precise array shapes and safer analysis. Install via Composer and enable with psalm-plugin.
composer require --dev php-standard-library/psalm-plugin
vendor/bin/psalm-plugin enable php-standard-library/psalm-plugin
vendor/bin/psalm --init
vendor/bin/psalm
Use the plugin to enforce precise type hints for PSL-coerced data. For example:
use Psl\Type;
$spec = Type\shape([
'name' => Type\string(),
'age' => Type\int(),
]);
$data = $spec->coerce($_GET['user']);
// Without plugin: Psalm infers `array<string|int, mixed>`
// With plugin: Psalm infers `array{name: string, age: int}`
psalm-plugin is enabled in psalm.config.php:
<?php
return [
'plugins' => [
'Psl\Psalm\Plugin',
],
];
Define PSL Shapes:
Use PSL’s Type namespace to define strict data contracts:
use Psl\Type;
$userShape = Type\shape([
'id' => Type\positive_int(),
'email' => Type\email_address(),
'roles' => Type\list_of(Type\string()),
]);
Coerce and Validate: Apply shapes to runtime data and let Psalm infer precise types:
$userData = $userShape->coerce($_POST['user']);
// Psalm now knows `$userData` is `array{id: positive-int, email: email-address, roles: list<string>}`
Leverage IDE Features: Psalm’s type inference enables autocompletion and error detection in IDEs (e.g., PHPStorm, VSCode).
Laravel-Specific: Use PSL shapes in Form Request validation or API resource contracts:
// app/Http/Requests/StoreUserRequest.php
public function rules(): array
{
return [
'user' => ['required', new PSLTypeRule($this->userShape)],
];
}
Note: Create a custom PSLTypeRule to bridge PSL shapes with Laravel’s validation.
CI/CD Pipeline: Add Psalm to your CI checks (e.g., GitHub Actions):
- name: Run Psalm with PSL Plugin
run: vendor/bin/psalm --init --no-cache
Testing: Use PSL shapes in PHPUnit tests to enforce type safety:
public function testUserShape(): void
{
$shape = Type\shape(['name' => Type\string()]);
$data = $shape->coerce(['name' => 'John']);
self::assertIsArray($data);
self::assertArrayHasKey('name', $data);
}
Migration Strategy:
Gradually replace runtime validation (e.g., Validator::make()) with PSL shapes where Psalm can statically verify correctness.
Plugin Compatibility:
psalm-plugin@^2.1 for Psalm 5).composer require php-standard-library/psalm-plugin@2.0.0
Type Inference Limits:
Type\any()).@psalm-suppress or refine shapes:
/** @psalm-suppress MixedAssignment */
$data = Type\any()->coerce($input);
Performance Overhead:
psalm.config.php:
return [
'exclude_paths' => ['tests', 'vendor'],
];
IDE Sync Issues:
Enable Verbose Output:
Run Psalm with --verbose to diagnose plugin issues:
vendor/bin/psalm --verbose
Check Plugin Status: Verify the plugin is enabled:
vendor/bin/psalm-plugin list
Isolate Problems: Test the plugin on a single file to isolate issues:
vendor/bin/psalm --init --no-cache path/to/file.php
Custom Return Type Providers: Extend the plugin by adding new return type providers for unsupported PSL functions. Example:
use Psalm\Plugin\ReturnTypeProviderInterface;
class CustomPslProvider implements ReturnTypeProviderInterface
{
public function getProvidedReturnType(): string
{
return 'array{...}';
}
public function getFunction(): string
{
return 'Psl\\Some\\Function';
}
}
Register it in psalm.config.php:
return [
'plugins' => [
'Psl\Psalm\Plugin',
'CustomPslProvider',
],
];
Psalm Configuration Overrides:
Override default plugin behavior via psalm.config.php:
return [
'plugins' => [
'Psl\Psalm\Plugin' => [
'strict_mode' => true,
],
],
];
Community Contributions:
Psl\Psalm\Plugin).Service Container Conflicts:
PSL shapes may clash with Laravel’s type hints (e.g., array vs. array{...}).
Solution: Use @psalm-type annotations for Laravel-specific types:
/** @psalm-type UserArray array{id: int, name: string} */
Eloquent Model Casting:
Combine PSL with Eloquent’s $casts to enforce type safety:
use Psl\Type;
protected $casts = [
'email' => 'string',
'active' => 'bool',
];
// In a PSL shape:
$userShape = Type\shape([
'email' => Type\email_address(),
'active' => Type\bool(),
]);
Request Validation: Use PSL shapes in API resources to enforce type contracts:
// app/Http/Resources/UserResource.php
public function toArray($request)
{
$shape = Type\shape(['id' => Type\int()]);
$data = $shape->coerce($this->resource);
return $data;
}
How can I help you explore Laravel packages today?